]> icculus.org git repositories - taylor/freespace2.git/blob - src/network/multi_pxo.cpp
silence various compiler warnings (clang:rel)
[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_freespace2_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 = 0;          
2604                 num_users = (ubyte)atoi(user_tok);              
2605
2606                 // if the # of users is > 0, or its not an autojoin, place it on the display list
2607                 if((num_users > 0) || !multi_pxo_is_autojoin(name_tok)){
2608                         // see if it exists already, and if so, just update the user count
2609                         lookup = multi_pxo_find_channel(name_tok,Multi_pxo_channels);
2610                         
2611                         if(lookup != NULL){
2612                                 lookup->num_users = (short)num_users;
2613                         }
2614                         // add the channel
2615                         else {
2616                                 res = multi_pxo_add_channel(name_tok,&Multi_pxo_channels);
2617                                 if(res != NULL){
2618                                         //Multi_pxo_channel_count++;
2619                                         res->num_users = (short)num_users;
2620                                         SDL_strlcpy(res->desc, desc_tok, SDL_arraysize(res->desc));
2621                                 }               
2622                         }
2623                 }                               
2624
2625                 // get the next name token
2626                 name_tok = strtok(NULL," ");
2627         } while(name_tok != NULL);
2628                 
2629         // if we need to autojoin, do so now
2630         //if(Multi_pxo_must_autojoin){
2631         //      Multi_pxo_must_autojoin = 0;
2632         //      
2633         //      multi_pxo_autojoin();
2634         //}
2635
2636         // refresh channels
2637         multi_pxo_set_status_text(XSTR("Connected to Parallax Online",951));    
2638
2639         // if we haven't refreshed server counts yet, do it now
2640         if(Multi_pxo_channel_server_refresh < 0.0f){
2641                 multi_pxo_channel_refresh_servers();
2642         }
2643
2644         // if we don't already have this guy on the list, add him
2645         if(ON_CHANNEL()){
2646                 pxo_channel *lookup;
2647                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2648                 if(lookup == NULL){
2649                         // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2650                         multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
2651                 }
2652         }
2653 }
2654
2655 // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2656 pxo_channel *multi_pxo_add_channel(char *name,pxo_channel **list)
2657 {
2658         pxo_channel *new_channel;
2659
2660         // try and allocate a new pxo_channel struct
2661         new_channel = (pxo_channel *)malloc(sizeof(pxo_channel));
2662         if ( new_channel == NULL ) {
2663                 nprintf(("Network", "Cannot allocate space for new pxo_channel structure\n"));
2664                 return NULL;
2665         }       
2666         memset(new_channel,0,sizeof(pxo_channel));
2667         // try and allocate a string for the channel name
2668         SDL_strlcpy(new_channel->name, name, SDL_arraysize(new_channel->name));
2669
2670         // insert it on the list
2671         if ( *list != NULL ) {
2672                 new_channel->next = (*list)->next;
2673                 new_channel->next->prev = new_channel;
2674                 (*list)->next = new_channel;
2675                 new_channel->prev = *list;
2676         } else {
2677                 *list = new_channel;
2678                 (*list)->next = (*list)->prev = *list;
2679         }
2680                 
2681         Multi_pxo_channel_count++;
2682         return new_channel;
2683 }
2684
2685 // lookup a channel with the specified name
2686 pxo_channel *multi_pxo_find_channel(char *name,pxo_channel *list)
2687 {
2688         pxo_channel *moveup;
2689
2690         // look the sucker up
2691         moveup = list;
2692         if(moveup == NULL){
2693                 return NULL;
2694         } 
2695         do {
2696                 if(!SDL_strcasecmp(name,moveup->name)){
2697                         return moveup;
2698                 }
2699
2700                 moveup = moveup->next;
2701         } while((moveup != list) && (moveup != NULL));
2702
2703         return NULL;
2704 }
2705
2706 // process the channel list (select, etc)
2707 void multi_pxo_process_channels()
2708 {
2709         int item_index,my;
2710         int idx;
2711         
2712         // if we don't have a start item, but the list is non-null
2713         if((Multi_pxo_channel_start == NULL) && (Multi_pxo_channels != NULL)){
2714                 Multi_pxo_channel_start = Multi_pxo_channels;
2715                 Multi_pxo_channel_start_index = 0;
2716         } 
2717
2718         // if we don't have a selected item, but the list is non-null
2719         if((Multi_pxo_channel_select == NULL) && (Multi_pxo_channels != NULL)){
2720                 Multi_pxo_channel_select = Multi_pxo_channels;
2721
2722                 // set the text
2723                 multi_pxo_set_status_text(Multi_pxo_channel_select->desc);
2724         }
2725
2726         // if the "switch" delay timestamp is set, see if it has expired
2727         if((Multi_pxo_switch_delay != -1) && timestamp_elapsed(Multi_pxo_switch_delay)){
2728                 Multi_pxo_switch_delay = -1;
2729         }
2730
2731         // see if we have a mouse click on the channel region
2732         if(Multi_pxo_channel_button.pressed()){
2733                 Multi_pxo_channel_button.get_mouse_pos(NULL,&my);
2734
2735                 // index from the top
2736                 item_index = my / 10;
2737
2738                 // select the item if possible
2739                 if((item_index + Multi_pxo_channel_start_index) < Multi_pxo_channel_count){
2740                         Multi_pxo_channel_select = Multi_pxo_channel_start;
2741                         for(idx=0;idx<item_index;idx++){
2742                                 Multi_pxo_channel_select = Multi_pxo_channel_select->next;
2743                         }
2744
2745                         // set the text
2746                         multi_pxo_set_status_text(Multi_pxo_channel_select->desc);
2747                 }
2748         }
2749
2750         // last refresh time
2751         if((Multi_pxo_channel_last_refresh > 0.0f) && ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_channel_last_refresh) > CHANNEL_REFRESH_TIME) ){
2752                 // refresh channels
2753                 multi_pxo_set_status_text(XSTR("Refreshing Public Channel List",952));                          
2754
2755                 // get a list of channels on the server (clear any old list as well)
2756                 multi_pxo_get_channels();
2757
2758                 // refresh
2759                 Multi_pxo_channel_last_refresh = -1.0f;
2760
2761                 nprintf(("Network","Refreshing channels\n"));
2762         }
2763
2764         // if we haven't updated our server channel counts in a while, do so again
2765         // last refresh time
2766         if((Multi_pxo_channel_server_refresh > 0.0f) && ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_channel_server_refresh) > CHANNEL_SERVER_REFRESH_TIME) ){
2767                 // refresh server counts
2768                 // multi_pxo_set_status_text("Refreshing Public Channel Server Counts");
2769
2770                 // do it _NOW_ I"M RIGHT HERE KILL ME WHAT ARE YOU WAITING FOR DO IT KILL ME DO IT NOW!
2771                 multi_pxo_channel_refresh_servers();            
2772         }       
2773 }
2774
2775 // send a request to refresh our channel server counts
2776 void multi_pxo_channel_refresh_servers()
2777 {
2778         pxo_channel *lookup;
2779         filter_game_list_struct filter;
2780         
2781         // traverse the list of existing channels we know about and query the game tracker about them
2782         lookup = Multi_pxo_channels;
2783         if(lookup == NULL){
2784                 return;
2785         }
2786         do {
2787                 if(strlen(lookup->name)){
2788                         // copy in the info
2789                         memset(&filter,0,sizeof(filter_game_list_struct));
2790                         SDL_strlcpy(filter.channel, lookup->name, SDL_arraysize(filter.channel));
2791                         
2792                         // send the request
2793                         RequestGameCountWithFilter(&filter);
2794                 }
2795
2796                 // next item
2797                 lookup = lookup->next;
2798         } while((lookup != NULL) && (lookup != Multi_pxo_channels));
2799
2800         // record the time
2801         Multi_pxo_channel_server_refresh = f2fl(timer_get_fixed_seconds());
2802 }
2803
2804 // refresh current channel server count
2805 void multi_pxo_channel_refresh_current()
2806 {
2807         // send a request for a server count on this channel
2808         if(strlen(Multi_pxo_channel_current.name)){
2809                 // fill in the data
2810                 filter_game_list_struct filter;
2811                 memset(&filter,0,sizeof(filter_game_list_struct));
2812                 SDL_strlcpy(filter.channel, Multi_pxo_channel_current.name, SDL_arraysize(filter.channel));
2813
2814                 // send the request
2815                 RequestGameCountWithFilter(&filter);
2816         }               
2817 }
2818
2819 // display the channel list
2820 void multi_pxo_blit_channels()
2821 {
2822         pxo_channel *moveup;
2823         char chan_name[255];
2824         char chan_users[15];
2825         char chan_servers[15];
2826         int user_w,server_w;
2827         int disp_count,y_start;
2828
2829         // blit as many channels as we can
2830         disp_count = 0;
2831         y_start = Multi_pxo_chan_coords[gr_screen.res][1];
2832         moveup = Multi_pxo_channel_start;
2833         if(moveup == NULL){
2834                 return;
2835         }
2836         do {            
2837                 // if this is the currently selected item, highlight it
2838                 if(moveup == Multi_pxo_channel_select){
2839                         gr_set_color_fast(&Color_bright);
2840                 }
2841                 // otherwise draw it normally
2842                 else {
2843                         gr_set_color_fast(&Color_normal);
2844                 }
2845
2846                 // get the # of users on the channel
2847                 SDL_snprintf(chan_users, SDL_arraysize(chan_users), "%d", moveup->num_users);
2848
2849                 // get the width of the user count string
2850                 gr_get_string_size(&user_w, NULL, chan_users);
2851
2852                 // get the # of servers on the channel
2853                 SDL_snprintf(chan_servers, SDL_arraysize(chan_servers), "%d", moveup->num_servers);
2854
2855                 // get the width of the user count string
2856                 gr_get_string_size(&server_w, NULL, chan_servers);
2857
2858                 // make sure the name fits
2859                 SDL_assert(moveup->name);
2860                 SDL_strlcpy(chan_name, moveup->name, SDL_arraysize(chan_name));
2861                 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]);
2862
2863                 // blit the strings
2864                 gr_string(Multi_pxo_chan_coords[gr_screen.res][0], y_start, chan_name + 1);
2865                 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);
2866                 gr_set_color_fast(&Color_bright);
2867                 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);
2868
2869                 // increment the displayed count
2870                 disp_count++;
2871                 y_start += 10;          
2872
2873                 // next item
2874                 moveup = moveup->next;
2875         } while((moveup != Multi_pxo_channels) && (disp_count < Multi_pxo_max_chan_display[gr_screen.res]));
2876 }
2877
2878 // scroll channel list up
2879 void multi_pxo_scroll_channels_up()
2880 {               
2881         // if we're already at the head of the list, do nothing
2882         if((Multi_pxo_channel_start == NULL) || (Multi_pxo_channel_start == Multi_pxo_channels)){
2883                 gamesnd_play_iface(SND_GENERAL_FAIL);           
2884                 return;
2885         }
2886         
2887         // otherwise move up one
2888         Multi_pxo_channel_start = Multi_pxo_channel_start->prev;
2889         Multi_pxo_channel_start_index--;
2890         gamesnd_play_iface(SND_USER_SELECT);
2891 }
2892
2893 // scroll channel list down
2894 void multi_pxo_scroll_channels_down()
2895 {
2896         // if we're already at the tail of the list, do nothing
2897         if((Multi_pxo_channel_start == NULL) || (Multi_pxo_channel_start->next == Multi_pxo_channels)){
2898                 gamesnd_play_iface(SND_GENERAL_FAIL);
2899                 return;
2900         }
2901
2902         // if we can't scroll further without going past the end of the viewable list, don't
2903         if((Multi_pxo_channel_start_index + Multi_pxo_max_chan_display[gr_screen.res]) >= Multi_pxo_channel_count){
2904                 gamesnd_play_iface(SND_GENERAL_FAIL);
2905                 return;
2906         }
2907
2908         // otherwise move down one
2909         Multi_pxo_channel_start = Multi_pxo_channel_start->next;
2910         Multi_pxo_channel_start_index++;
2911         gamesnd_play_iface(SND_USER_SELECT);
2912 }
2913
2914 // attempt to join a channel
2915 void multi_pxo_join_channel(pxo_channel *chan)
2916 {       
2917         char switch_msg[256];
2918         
2919         // if we're already on this channel, do nothing
2920         if(ON_CHANNEL() && !SDL_strcasecmp(chan->name,Multi_pxo_channel_current.name)){
2921                 return;
2922         }
2923
2924         // if we're already trying to join a channel, do nothing
2925         if(SWITCHING_CHANNELS()){
2926                 return;
2927         }
2928
2929         // try and join the channel     
2930         switch(SetNewChatChannel(chan->name)){
2931         case -1 :
2932                 Int3();
2933                 break;
2934                 
2935         case 0 :
2936                 // decrement the count of our current channel
2937                 pxo_channel *lookup;
2938                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2939                 if(lookup != NULL){
2940                         lookup->num_users--;
2941                 }
2942
2943                 // set our current channel as none
2944                 memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
2945                 Multi_pxo_channel_current.num_users = -1;
2946
2947                 multi_pxo_set_status_text(XSTR("Switching channels",953));
2948
2949                 // copy the channel
2950                 memcpy(&Multi_pxo_channel_switch,chan,sizeof(pxo_channel));
2951
2952                 // clear the player list
2953                 multi_pxo_clear_players();
2954
2955                 // display a line of text indicating that we're switching channels
2956                 if(strlen(Multi_pxo_channel_switch.name) > 1){
2957                         SDL_snprintf(switch_msg, SDL_arraysize(switch_msg), "[Switching to channel %s]", Multi_pxo_channel_switch.name + 1);
2958                 } else {
2959                         SDL_snprintf(switch_msg, SDL_arraysize(switch_msg), "[Switching to channel %s]", Multi_pxo_channel_switch.name);
2960                 }
2961                 multi_pxo_chat_process_incoming(switch_msg, CHAT_MODE_CHANNEL_SWITCH);
2962                 break;
2963
2964         case 1 :
2965                 Int3();         
2966         }       
2967 }
2968
2969 // handle any processing details if we're currently trying to join a channel
2970 void multi_pxo_handle_channel_change()
2971 {                       
2972         // if we're not switching channels, do nothing
2973         if(!SWITCHING_CHANNELS()){
2974                 return;
2975         }
2976
2977         // if we are, check the status
2978         switch(SetNewChatChannel(NULL)){
2979         // failed to switch
2980         case -1 :
2981                 // unset our switching struct
2982                 memset(&Multi_pxo_channel_switch,0,sizeof(pxo_channel));
2983                 Multi_pxo_channel_switch.num_users = -1;
2984
2985                 // notify of error
2986                 multi_pxo_set_status_text(XSTR("No channel (error while switching)",954));
2987                 break;
2988
2989         // still switching
2990         case 0:
2991                 break;
2992
2993         // successfully changed
2994         case 1:
2995                 // copy the current channel info, and unset the switching status
2996                 memcpy(&Multi_pxo_channel_current,&Multi_pxo_channel_switch,sizeof(pxo_channel));
2997                 Multi_pxo_channel_switch.num_users = -1;
2998
2999                 // set our "last" channel
3000                 SDL_strlcpy(Multi_pxo_channel_last, Multi_pxo_channel_current.name, SDL_arraysize(Multi_pxo_channel_last));
3001
3002                 // notify the user              
3003                 multi_pxo_set_status_text(XSTR("Connected to Parallax Online",951));
3004
3005                 // if we don't already have this guy on the list, add him
3006                 pxo_channel *lookup;
3007                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
3008                 if(lookup == NULL){
3009                         // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
3010                         lookup = multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
3011                 }
3012
3013                 // set the user count to be 1 (just me)
3014                 if(lookup != NULL){
3015                         lookup->num_users = 1;
3016                 }
3017
3018                 // clear the chat area
3019                 // multi_pxo_chat_clear();              
3020
3021                 // set the "switch" delay timestamp
3022                 Multi_pxo_switch_delay = timestamp(MULTI_PXO_SWITCH_DELAY_TIME);
3023
3024                 // refresh current channel server count
3025                 multi_pxo_channel_refresh_current();            
3026                 break;
3027         }
3028 }
3029
3030
3031 // player related stuff -------------------------------------------
3032
3033 // clear the old player list
3034 void multi_pxo_clear_players()
3035 {
3036         player_list *moveup,*backup;
3037         
3038         // if the list is null, don't free it up
3039         if(Multi_pxo_players != NULL){          
3040                 // otherwise
3041                 moveup = Multi_pxo_players;
3042                 backup = NULL;
3043                 if(moveup != NULL){
3044                         do {                    
3045                                 backup = moveup;
3046                                 moveup = moveup->next;                  
3047                 
3048                                 // free the struct itself
3049                                 free(backup);
3050                                 backup = NULL;
3051                         } while(moveup != Multi_pxo_players);
3052                         Multi_pxo_players = NULL;
3053                 }       
3054         }
3055
3056         Multi_pxo_player_start = NULL;  
3057         // Multi_pxo_player_start_index = -1;
3058         Multi_pxo_player_select = NULL;
3059
3060         // reset the slider
3061         // Multi_pxo_player_slider.set_numberItems(0);
3062
3063         // add a bunch of bogus players
3064         /*
3065         char str[50];
3066         for(int idx=0;idx<30;idx++){
3067                 sprintf(str,"player%d",idx);
3068                 multi_pxo_add_player(str);
3069         }
3070         */
3071 }
3072
3073 // create a new player with the given name and place it on the player list, return a pointer or NULL on fail
3074 player_list *multi_pxo_add_player(char *name)
3075 {
3076         player_list *new_player;
3077
3078         // try and allocate a new player_list struct
3079         new_player = (player_list *)malloc(sizeof(player_list));
3080         if ( new_player == NULL ) {
3081                 nprintf(("Network", "Cannot allocate space for new player_list structure\n"));
3082                 return NULL;
3083         }       
3084         // try and allocate a string for the channel name
3085         SDL_strlcpy(new_player->name, name, SDL_arraysize(new_player->name));
3086
3087         // insert it on the list
3088         if ( Multi_pxo_players != NULL ) {
3089                 new_player->next = Multi_pxo_players->next;
3090                 new_player->next->prev = new_player;
3091                 Multi_pxo_players->next = new_player;
3092                 new_player->prev = Multi_pxo_players;           
3093         } else {
3094                 Multi_pxo_players = new_player;
3095                 Multi_pxo_players->next = Multi_pxo_players->prev = Multi_pxo_players;
3096         }
3097
3098         // increment the start position
3099         if(Multi_pxo_player_start != NULL){
3100                 // Multi_pxo_player_start_index++;
3101         }
3102
3103         // new player
3104         Multi_pxo_player_count++;
3105
3106         // update the count on the slider
3107         // Multi_pxo_player_slider.set_numberItems(Multi_pxo_player_count);
3108                 
3109         return new_player;
3110 }
3111
3112 // remove a player with the given name
3113 void multi_pxo_del_player(char *name)
3114 {
3115         player_list *lookup;
3116
3117         // try and find this guy
3118         lookup = Multi_pxo_players;
3119         if(lookup == NULL){
3120                 return;
3121         }
3122         do {
3123                 // if we found a match, delete it
3124                 if(!SDL_strcasecmp(name,lookup->name)){
3125                         // if this is the only item on the list, free stuff up
3126                         if(lookup->next == lookup){
3127                                 SDL_assert(lookup == Multi_pxo_players);
3128                                 free(lookup);
3129                                 Multi_pxo_players = NULL;
3130                                 multi_pxo_clear_players();
3131                         }
3132                         // otherwise, just delete it 
3133                         else {
3134                                 lookup->next->prev = lookup->prev;
3135                                 lookup->prev->next = lookup->next;                              
3136                                 
3137                                 // if this was our selected item, unselect it
3138                                 if((lookup == Multi_pxo_player_select) && (Multi_pxo_player_select != NULL)){
3139                                         Multi_pxo_player_select = Multi_pxo_player_select->next;
3140                                 }
3141
3142                                 // if this was our point to start viewing from, select another
3143                                 if(lookup == Multi_pxo_player_start){
3144                                         // if this is the head of the list, move up one
3145                                         if(Multi_pxo_players == lookup){
3146                                                 Multi_pxo_player_start = Multi_pxo_player_start->next;
3147                                                 // Multi_pxo_player_start_index = 0;
3148                                         }
3149                                         // otherwise move back
3150                                         else {
3151                                                 Multi_pxo_player_start = Multi_pxo_player_start->prev;
3152                                                 // Multi_pxo_player_start_index++;
3153                                         }
3154                                 }
3155
3156                                 // if this is the head of the list, move it up
3157                                 if(lookup == Multi_pxo_players){
3158                                         Multi_pxo_players = Multi_pxo_players->next;                                    
3159                                 }
3160
3161                                 // free the item up
3162                                 lookup->next = NULL;
3163                                 lookup->prev = NULL;
3164                                 free(lookup);
3165                         }       
3166
3167                         // new player
3168                         Multi_pxo_player_count--;
3169                         SDL_assert(Multi_pxo_player_count >= 0);
3170
3171                         // update the count on the slider
3172                         // Multi_pxo_player_slider.set_numberItems(Multi_pxo_player_count);
3173                         // Multi_pxo_player_slider.force_currentItem(Multi_pxo_player_start_index);
3174                                 
3175                         // we're done now
3176                         return;
3177                 }
3178
3179                 // next item
3180                 lookup = lookup->next;
3181         } while((lookup != NULL) && (lookup != Multi_pxo_players));
3182 }
3183
3184 // try and find a player with the given name, return a pointer to his entry (or NULL)
3185 player_list *multi_pxo_find_player(char *name)
3186 {
3187         player_list *lookup;
3188
3189         // look through all players
3190         lookup = Multi_pxo_players;
3191         if(lookup == NULL){
3192                 return NULL;
3193         } 
3194         do {    
3195                 if(!SDL_strcasecmp(name,lookup->name)){
3196                         return lookup;
3197                 }
3198
3199                 lookup = lookup->next;
3200         } while((lookup != NULL) && (lookup != Multi_pxo_players));
3201
3202         // return NULL
3203         return NULL;
3204 }
3205
3206 // process the player list (select, etc)
3207 void multi_pxo_process_players()
3208 {
3209         int item_index,my;
3210         player_list *lookup;
3211         
3212         // if we don't have a start item, but the list is non-null
3213         if((Multi_pxo_player_start == NULL) && (Multi_pxo_players != NULL)){
3214                 Multi_pxo_player_start = Multi_pxo_players;             
3215                 // Multi_pxo_player_start_index = 0;
3216
3217                 // update the slider
3218                 // Multi_pxo_player_slider.set_currentItem(Multi_pxo_player_start_index);
3219         } 
3220
3221         // if we don't have a selected item, but the list is non-null
3222         if((Multi_pxo_player_select == NULL) && (Multi_pxo_players != NULL)){
3223                 Multi_pxo_player_select = Multi_pxo_players;
3224         }
3225
3226         // see if we have a mouse click on the channel region
3227         if(Multi_pxo_player_button.pressed()){
3228                 Multi_pxo_player_button.get_mouse_pos(NULL,&my);
3229
3230                 // index from the top
3231                 item_index = my / 10;
3232
3233                 // select the item if possible
3234                 lookup = Multi_pxo_player_start;
3235                 if(lookup == NULL){
3236                         return;
3237                 }
3238                 // select item 0
3239                 if(item_index == 0){
3240                         Multi_pxo_player_select = Multi_pxo_player_start;
3241                         return;
3242                 }
3243                 do {
3244                         // move to the next item
3245                         lookup = lookup->next;
3246                         item_index--;
3247
3248                         // if this item is our guy
3249                         if((item_index == 0) && (lookup != Multi_pxo_players)){
3250                                 Multi_pxo_player_select = lookup;
3251                                 return;
3252                         }
3253                 } while((lookup != Multi_pxo_players) && (item_index > 0));             
3254         }
3255 }
3256
3257 // display the player list
3258 void multi_pxo_blit_players()
3259 {
3260         player_list *moveup;
3261         char player_name[255];
3262         int disp_count,y_start;
3263
3264         // blit as many channels as we can
3265         disp_count = 0;
3266         y_start = Multi_pxo_player_coords[gr_screen.res][1];
3267         moveup = Multi_pxo_player_start;
3268         if(moveup == NULL){
3269                 return;
3270         }
3271         do {
3272                 // if this is the currently selected item, highlight it
3273                 if(moveup == Multi_pxo_player_select){
3274                         gr_set_color_fast(&Color_bright);
3275                 }
3276                 // otherwise draw it normally
3277                 else {
3278                         gr_set_color_fast(&Color_normal);
3279                 }
3280
3281                 // make sure the string fits            
3282                 SDL_strlcpy(player_name, moveup->name, SDL_arraysize(player_name));
3283                 gr_force_fit_string(player_name, 254, Multi_pxo_player_coords[gr_screen.res][2]);
3284
3285                 // blit the string
3286                 gr_string(Multi_pxo_player_coords[gr_screen.res][0], y_start, player_name);
3287
3288                 // increment the displayed count
3289                 disp_count++;
3290                 y_start += 10;
3291
3292                 // next item
3293                 moveup = moveup->next;
3294         } while((moveup != Multi_pxo_players) && (disp_count < Multi_pxo_max_player_display[gr_screen.res]));
3295 }
3296
3297 // scroll player list up
3298 void multi_pxo_scroll_players_up()
3299 {
3300         // if we're already at the head of the list, do nothing
3301         if((Multi_pxo_player_start == NULL) || (Multi_pxo_player_start == Multi_pxo_players)){
3302                 gamesnd_play_iface(SND_GENERAL_FAIL);           
3303                 return;
3304         }
3305         
3306         // otherwise move up one
3307         Multi_pxo_player_start = Multi_pxo_player_start->prev;  
3308         // Multi_pxo_player_start_index--;
3309         // SDL_assert(Multi_pxo_player_start_index >= 0);
3310
3311         gamesnd_play_iface(SND_USER_SELECT);
3312 }
3313
3314 // scroll player list down
3315 void multi_pxo_scroll_players_down()
3316 {
3317         player_list *lookup;
3318         int count = 0;
3319         
3320         // see if its okay to scroll down
3321         lookup = Multi_pxo_player_start;
3322         if(lookup == NULL ){
3323                 gamesnd_play_iface(SND_GENERAL_FAIL);
3324                 return;
3325         }
3326         count = 0;
3327         while(lookup->next != Multi_pxo_players){
3328                 lookup = lookup->next;
3329                 count++;
3330         }
3331         
3332         // if we can move down
3333         if(count >= Multi_pxo_max_player_display[gr_screen.res]){
3334                 Multi_pxo_player_start = Multi_pxo_player_start->next;
3335
3336                 // Multi_pxo_player_start_index++;
3337
3338                 gamesnd_play_iface(SND_USER_SELECT);
3339         } else {
3340                 gamesnd_play_iface(SND_GENERAL_FAIL);
3341         }       
3342 }
3343
3344
3345 // chat text stuff -----------------------------------------
3346
3347 // initialize and create the chat text linked list
3348 void multi_pxo_chat_init()
3349 {
3350         int idx;
3351         chat_line *new_line;
3352
3353         // no chat lines
3354         Multi_pxo_chat = NULL;
3355         Multi_pxo_chat_add = NULL;
3356         Multi_pxo_chat_start = NULL;
3357         Multi_pxo_chat_start_index = -1;
3358
3359         // create the lines in a non-circular doubly linked list
3360         for(idx=0;idx<MAX_CHAT_LINES;idx++){
3361                 new_line = (chat_line*)malloc(sizeof(chat_line));       
3362                 
3363                 // clear the line out
3364                 SDL_assert(new_line != NULL);
3365                 if(new_line == NULL){
3366                         return;
3367                 }
3368                 memset(new_line,0,sizeof(chat_line));
3369                 new_line->prev = NULL;
3370                 new_line->next = NULL;          
3371
3372                 // insert it into the (empty) list
3373                 if(Multi_pxo_chat == NULL){
3374                         Multi_pxo_chat = new_line;
3375                 }
3376                 // insert it onto the (non-empty) list
3377                 else {
3378                         Multi_pxo_chat->prev = new_line;
3379                         new_line->next = Multi_pxo_chat;
3380                         Multi_pxo_chat = new_line;
3381                 }
3382         }
3383
3384         // start adding chat lines at the beginning of the list
3385         Multi_pxo_chat_add = Multi_pxo_chat;
3386 }
3387
3388 // free up all chat list stuff
3389 void multi_pxo_chat_free()
3390 {
3391         chat_line *moveup, *backup;     
3392
3393         // free all items up
3394         moveup = Multi_pxo_chat;
3395         while(moveup != NULL){
3396                 backup = moveup;                
3397                 moveup = moveup->next;
3398
3399                 free(backup);
3400         }
3401
3402         // no chat lines
3403         Multi_pxo_chat = NULL;
3404         Multi_pxo_chat_add = NULL;
3405         Multi_pxo_chat_start = NULL;
3406         Multi_pxo_chat_start_index = -1;
3407         Multi_pxo_chat_count = 0;
3408         Multi_pxo_chat_slider.set_numberItems(0);       
3409 }
3410
3411 // clear all lines of chat text in the chat area
3412 void multi_pxo_chat_clear()
3413 {
3414         chat_line *moveup;
3415
3416         // clear the text in all the lines
3417         moveup = Multi_pxo_chat;
3418         while(moveup != NULL){
3419                 SDL_zero(moveup->text);
3420                 moveup = moveup->next;
3421         }
3422
3423         // how many chat lines we have
3424         Multi_pxo_chat_count = 0;
3425
3426         // start adding chat lines at the beginning of the list
3427         Multi_pxo_chat_add = Multi_pxo_chat;
3428 }
3429
3430 // add a line of text
3431 void multi_pxo_chat_add_line(char *txt, int mode)
3432 {
3433         chat_line *temp;
3434         
3435         // copy in the text
3436         SDL_assert(Multi_pxo_chat_add != NULL);
3437         SDL_strlcpy(Multi_pxo_chat_add->text, txt, SDL_arraysize(Multi_pxo_chat_add->text));
3438         Multi_pxo_chat_add->mode = mode;
3439
3440         // if we're at the end of the list, move the front item down
3441         if(Multi_pxo_chat_add->next == NULL) {
3442                 // store the new "head" of the list
3443                 temp = Multi_pxo_chat->next;
3444
3445                 // move the current head to the end of the list
3446                 Multi_pxo_chat_add->next = Multi_pxo_chat;
3447                 temp->prev = NULL;              
3448                 Multi_pxo_chat->prev = Multi_pxo_chat_add;
3449                 Multi_pxo_chat->next = NULL;
3450
3451                 // reset the head of the list
3452                 Multi_pxo_chat = temp;
3453
3454                 // set the new add line
3455                 Multi_pxo_chat_add = Multi_pxo_chat_add->next;
3456                 SDL_zero(Multi_pxo_chat_add->text);
3457                 Multi_pxo_chat_add->mode = CHAT_MODE_NORMAL;
3458         } 
3459         // if we're not at the end of the list, just move up by one
3460         else {
3461                 // set the new add line
3462                 Multi_pxo_chat_add = Multi_pxo_chat_add->next;
3463         }
3464
3465         // if we've reached max chat lines, don't increment
3466         if(Multi_pxo_chat_count < MAX_CHAT_LINES) {
3467                 Multi_pxo_chat_count++;
3468         }
3469
3470         // set the count
3471         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
3472
3473         // force the position, in case we arent at the bottom of the list
3474
3475
3476         // move to the bottom of the chat area
3477         /*
3478         if(from_self){
3479                 multi_pxo_goto_bottom();
3480         }       
3481         else {          
3482                 // if we have more than the # of lines displayable
3483                 if(Multi_pxo_chat_count >= MULTI_PXO_MAX_CHAT_DISPLAY){
3484                 }
3485                 multi_pxo_goto_bottom();
3486         }
3487         */
3488         multi_pxo_goto_bottom();
3489 }
3490
3491 // process an incoming line of text
3492 void multi_pxo_chat_process_incoming(const char *txt,int mode)
3493 {
3494         char msg_total[512],line[512];
3495         int     n_lines,idx;
3496         int     n_chars[20];
3497         char    *p_str[20];                     //  the initial line (unindented)       
3498         const char *priv_ptr = NULL;
3499
3500         // filter out "has left" channel messages, when switching channels
3501         if((SWITCHING_CHANNELS() || ((Multi_pxo_switch_delay != -1) && !timestamp_elapsed(Multi_pxo_switch_delay))) && 
3502                 multi_pxo_chat_is_left_message(txt)){
3503                 return;
3504         }
3505                 
3506         // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL
3507         priv_ptr = multi_pxo_chat_is_private(txt);
3508         if(priv_ptr != NULL){           
3509                 SDL_strlcpy(msg_total, priv_ptr, SDL_arraysize(msg_total));
3510         } else {
3511                 SDL_strlcpy(msg_total, txt, SDL_arraysize(msg_total));
3512         }       
3513
3514         // determine what mode to display this text in
3515
3516         // if this is private chat
3517         if(priv_ptr != NULL){
3518                 mode = CHAT_MODE_PRIVATE;
3519         }
3520         // all other chat
3521         else {
3522                 // if this is a server message
3523                 if(multi_pxo_is_server_text(txt)){
3524                         mode = CHAT_MODE_SERVER;
3525                 }
3526                 // if this is a MOTD
3527                 else if(multi_pxo_is_motd_text(txt)){
3528                         // mode = CHAT_MODE_MOTD;
3529                         // stuff the motd
3530                         multi_pxo_motd_add_text(txt);
3531                         return;
3532                 } 
3533                 // if this is the end of motd text
3534                 else if(multi_pxo_is_end_of_motd_text(txt)){
3535                         multi_pxo_set_end_of_motd();
3536                         return;
3537                 }
3538         }
3539
3540         // split the text up into as many lines as necessary
3541         n_lines = split_str(msg_total, Multi_pxo_chat_coords[gr_screen.res][2] - 5, n_chars, p_str, 3);
3542         SDL_assert((n_lines != -1) && (n_lines <= 20));
3543         if((n_lines < 0) || (n_lines > 20)) {
3544                 return;
3545         }
3546
3547         // if the string fits on one line
3548         if(n_lines == 1) {
3549                 multi_pxo_chat_add_line(msg_total,mode);
3550
3551                 // don't pad with extra spaces if its from the server
3552                 /*
3553                 if(mode != CHAT_MODE_SERVER){
3554                         multi_pxo_chat_add_line("",CHAT_MODE_NORMAL);
3555                 }
3556                 */
3557         }
3558         // if the string was split into multiple lines
3559         else {
3560                 // add the first line           
3561                 memcpy(line,p_str[0],n_chars[0]);
3562                 line[n_chars[0]] = '\0';
3563                 multi_pxo_chat_add_line(line,mode);
3564
3565                 // copy the rest of the lines
3566                 for(idx=1; idx<n_lines; idx++){
3567                         memcpy(line,p_str[idx],n_chars[idx]);
3568                         line[n_chars[idx]] = '\0';                      
3569                         
3570                         // unless the current mode is server or "switching channels", make all these CHAT_MODE_CARRY
3571                         if((mode != CHAT_MODE_SERVER) && (mode != CHAT_MODE_CHANNEL_SWITCH)){
3572                                 mode = CHAT_MODE_CARRY;
3573                         }                       
3574                         multi_pxo_chat_add_line(line, mode);
3575                 }
3576         }       
3577 }
3578
3579 // blit the chat text
3580 void multi_pxo_chat_blit()
3581 {
3582         int y_start;
3583         int disp_count,token_width;
3584         char piece[100];
3585         char title[255];
3586         char *tok;
3587         chat_line *moveup;
3588
3589         // blit the title line
3590         if(ON_CHANNEL()){
3591                 if(strlen(Multi_pxo_channel_current.name) > 1){
3592                         SDL_snprintf(title, SDL_arraysize(title), XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name+1);  // [[ <who> on <channel> ]]
3593                 } else {
3594                         SDL_snprintf(title, SDL_arraysize(title), XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name);         // [[ <who> on <channel> ]]
3595                 }
3596         } else {
3597                 SDL_strlcpy(title, XSTR("Parallax Online - No Channel", 956), SDL_arraysize(title));
3598         }       
3599         gr_force_fit_string(title, 254, Multi_pxo_chat_coords[gr_screen.res][2] - 10);
3600         gr_get_string_size(&token_width,NULL,title);
3601         gr_set_color_fast(&Color_normal);
3602         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); 
3603
3604         // blit all active lines of text
3605         moveup = Multi_pxo_chat_start;  
3606         disp_count = 0;
3607         y_start = Multi_pxo_chat_coords[gr_screen.res][1];
3608         while((moveup != NULL) && (moveup != Multi_pxo_chat_add) && (disp_count < (Multi_pxo_max_chat_display[gr_screen.res]))){
3609                 switch(moveup->mode){
3610                 // if this is text from the server, display it all "bright"
3611                 case CHAT_MODE_SERVER:                          
3612                         gr_set_color_fast(&Color_bright);
3613                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3614                         break;
3615
3616                 // if this is motd, display it all "bright"
3617                 case CHAT_MODE_MOTD:
3618                         gr_set_color_fast(&Color_bright_white);
3619                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3620                         break;
3621
3622                 // normal mode, just highlight the server
3623                 case CHAT_MODE_PRIVATE:         
3624                 case CHAT_MODE_NORMAL:                                  
3625                         SDL_strlcpy(piece, moveup->text, SDL_arraysize(piece));
3626                         tok = strtok(piece," ");
3627                         if(tok != NULL){
3628                                 // get the width of just the first "piece"
3629                                 gr_get_string_size(&token_width, NULL, tok);
3630                                 
3631                                 // draw it brightly
3632                                 gr_set_color_fast(&Color_bright);
3633                                 gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, tok);
3634
3635                                 // draw the rest of the string normally
3636                                 tok = strtok(NULL,"");
3637                                 if(tok != NULL){
3638                                         gr_set_color_fast(&Color_normal);
3639                                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0] + token_width + 6, y_start, tok);
3640                                 }
3641                         }
3642                         break;
3643                 
3644                 // carry mode, display with no highlight
3645                 case CHAT_MODE_CARRY:
3646                         gr_set_color_fast(&Color_normal);
3647                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3648                         break;
3649
3650                 // "switching channels mode", display it bright
3651                 case CHAT_MODE_CHANNEL_SWITCH:
3652                         gr_set_color_fast(&Color_bright);
3653                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3654                         break;
3655                 }
3656                 
3657                 // next chat line
3658                 moveup = moveup->next;
3659                 disp_count++;
3660                 y_start += 10;
3661         }
3662
3663         if ((moveup != Multi_pxo_chat_add) && (moveup != NULL)) {
3664                 Can_scroll_down = 1;
3665         } else {
3666                 Can_scroll_down = 0;
3667         }
3668 }
3669
3670 // scroll to the very bottom of the chat area
3671 void multi_pxo_goto_bottom()
3672 {
3673         chat_line *backup;
3674         int idx;
3675
3676         if (Multi_pxo_chat == NULL) {
3677                 return;
3678         }
3679         
3680         // if we have less than the displayable amount of lines, do nothing
3681         if(Multi_pxo_chat_count <= Multi_pxo_max_chat_display[gr_screen.res]){
3682                 Multi_pxo_chat_start = Multi_pxo_chat;                                          
3683                 
3684                 // nothing to do for the slider
3685                 Multi_pxo_chat_slider.set_numberItems(0);
3686                 return;
3687         }
3688
3689         if (!Can_scroll_down)
3690         {
3691                 // otherwise move back the right # of items
3692                 backup = Multi_pxo_chat_add;    
3693                 for(idx=0; idx<Multi_pxo_max_chat_display[gr_screen.res]; idx++){
3694                         SDL_assert(backup->prev != NULL);
3695                         backup = backup->prev;          
3696                 }
3697
3698                 Multi_pxo_chat_start = backup;
3699
3700                 // fixup the start index
3701                 multi_pxo_chat_adjust_start();  
3702         }
3703 }
3704
3705 // scroll the text up
3706 void multi_pxo_scroll_chat_up()
3707 {
3708         // if we're already at the top of the list, don't do anything   
3709         if ((Multi_pxo_chat_start == NULL) || (Multi_pxo_chat_start == Multi_pxo_chat)) {
3710                 gamesnd_play_iface(SND_GENERAL_FAIL);
3711                 return;
3712         }
3713
3714         // otherwise move up one
3715         Multi_pxo_chat_start = Multi_pxo_chat_start->prev;      
3716
3717         multi_pxo_chat_adjust_start();  
3718         
3719         gamesnd_play_iface(SND_USER_SELECT);
3720 }
3721
3722 // returns 1 if we can scroll down, 0 otherwise
3723 int multi_pxo_can_scroll_down()
3724 {
3725         chat_line *lookup;
3726         int count = 0;
3727         
3728         // see if its okay to scroll down
3729         lookup = Multi_pxo_chat_start;
3730         if (lookup == NULL) {
3731         //      gamesnd_play_iface(SND_GENERAL_FAIL);
3732                 return 0;
3733         }
3734         count = 0;
3735         while (lookup != Multi_pxo_chat_add) {
3736                 lookup = lookup->next;
3737                 count++;
3738         }
3739         
3740         // check if we can move down, return accordingly
3741         if (count > Multi_pxo_max_chat_display[gr_screen.res]) {
3742                 return 1;
3743         } else {
3744                 return 0;
3745         }
3746 }
3747
3748 // scroll the text down
3749 void multi_pxo_scroll_chat_down()
3750 {
3751         // if we can move down
3752         if (multi_pxo_can_scroll_down()) {
3753                 Multi_pxo_chat_start = Multi_pxo_chat_start->next;              
3754                 multi_pxo_chat_adjust_start();  
3755                 gamesnd_play_iface(SND_USER_SELECT);
3756         } else {
3757                 gamesnd_play_iface(SND_GENERAL_FAIL);
3758         }
3759 }
3760
3761 // process chat controls
3762 void multi_pxo_chat_process()
3763 {
3764         char *remainder = NULL;
3765         const char *result = NULL;
3766         char msg[512];
3767         int msg_pixel_width;
3768
3769         // if the chat line is getting too long, fire off the message, putting the last
3770         // word on the next input line.
3771         SDL_zero(msg);
3772         Multi_pxo_chat_input.get_text(msg);
3773
3774         // determine if the width of the string in pixels is > than the inputbox width -- if so,
3775         // then send the message
3776         gr_get_string_size(&msg_pixel_width, NULL, msg);
3777         // if ( msg_pixel_width >= (Chatbox_inputbox_w - Player->short_callsign_width) ) {
3778         if ( msg_pixel_width >= (Multi_pxo_input_coords[gr_screen.res][2])) {
3779                 remainder = strrchr(msg, ' ');
3780                 if ( remainder ) {
3781                         *remainder = '\0';
3782                         remainder++;
3783                 }       
3784                 
3785                 // if we're connected to a channel, send the chat to the server
3786                 if(ON_CHANNEL()){
3787                         result = SendChatString(msg,1);
3788                         if(result != NULL){
3789                                 multi_pxo_chat_process_incoming(result);
3790                         }
3791
3792                         // display any remainder of text on the next line
3793                         Multi_pxo_chat_input.set_text( remainder ? remainder : "" );
3794                 } else {
3795                         Multi_pxo_chat_input.set_text("");
3796                 }
3797         } else if((Multi_pxo_chat_input.pressed() && (strlen(msg) > 0)) || (strlen(msg) >= MAX_CHAT_LINE_LEN)) { 
3798                 // tack on the null terminator in the boundary case
3799                 int x = strlen(msg);
3800                 if(x >= MAX_CHAT_LINE_LEN){
3801                         msg[MAX_CHAT_LINE_LEN-1] = '\0';
3802                 }               
3803
3804                 // ignore "/nick" commands
3805                 if(multi_pxo_is_nick_command(msg)){
3806                         Multi_pxo_chat_input.set_text("");
3807                         return;
3808                 }
3809                 
3810                 // send the chat to the server                  
3811                 // if we're connected to a channel, send the chat to the server
3812                 if(ON_CHANNEL()){               
3813                         result = SendChatString(msg,1);
3814                         if(result != NULL){
3815                                 multi_pxo_chat_process_incoming(result);
3816                         }
3817
3818                         // display any remainder of text on the next line
3819                         Multi_pxo_chat_input.set_text( remainder ? remainder : "" );
3820                 } else {
3821                         Multi_pxo_chat_input.set_text("");
3822                 }
3823         }       
3824 }
3825
3826 // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL
3827 //XSTR:OFF
3828
3829 // NOTE : DO NOT LOCALIZE THESE STRINGS!!!! THEY ARE CONSTANTS WHICH ARE CHECKED AGAINST 
3830 // PXO CHAT SERVER DATA. THEY CANNOT CHANGE!!!
3831 #define PMSG_FROM                       "private message from "
3832 #define PMSG_TO                 "private message to "
3833 const char *multi_pxo_chat_is_private(const char *txt)
3834 {
3835         // quick check
3836         if( strlen(txt) > strlen( PMSG_FROM ) ){        
3837                 // otherwise do a comparison
3838                 if(!SDL_strncasecmp( txt, PMSG_FROM, strlen(PMSG_FROM) )){
3839                         return &txt[strlen( PMSG_FROM )];
3840                 } 
3841         }
3842
3843         // quick check
3844         if(strlen(txt) > strlen( PMSG_TO )){    
3845                 // otherwise do a comparison
3846                 if(!SDL_strncasecmp(txt,PMSG_TO,strlen(PMSG_TO))){
3847                         return &txt[strlen(PMSG_TO)];
3848                 } 
3849         }
3850         
3851         return NULL;
3852 }
3853 //XSTR:ON
3854
3855 // if the text came from the server
3856 int multi_pxo_is_server_text(const char *txt)
3857 {               
3858         // if the message is prefaced by a ***
3859         if((strlen(txt) >= strlen(MULTI_PXO_SERVER_PREFIX)) && !strncmp(txt, MULTI_PXO_SERVER_PREFIX, strlen(MULTI_PXO_SERVER_PREFIX))){
3860                 return 1;
3861         }
3862
3863         return 0;
3864 }
3865
3866 // if the text is message of the day text
3867 int multi_pxo_is_motd_text(const char *txt)
3868 {
3869         // if we're not on a channel, and this is not a channel switching message assume its coming from a server
3870         if((strlen(txt) >= strlen(PXO_CHAT_MOTD_PREFIX)) && !strncmp(txt, PXO_CHAT_MOTD_PREFIX, strlen(PXO_CHAT_MOTD_PREFIX))){
3871                 return 1;
3872         }       
3873         
3874         return 0;
3875 }
3876
3877 // if the text is the end of motd text
3878 int multi_pxo_is_end_of_motd_text(const char *txt)
3879 {
3880         // if we're not on a channel, and this is not a channel switching message assume its coming from a server
3881         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))){
3882                 return 1;
3883         }       
3884         
3885         return 0;
3886 }
3887
3888 // if the text is a "has left message" from the server
3889 int multi_pxo_chat_is_left_message(const char *txt)
3890 {
3891         char last_portion[100];
3892         
3893         // if the text is not server text
3894         if(!multi_pxo_is_server_text(txt)){
3895                 return 0;
3896         }
3897
3898         // check to see if the last portion is the correct wording
3899         SDL_zero(last_portion);
3900         if((strlen(txt) > strlen(MULTI_PXO_HAS_LEFT)) && !strcmp(&txt[strlen(txt) - strlen(MULTI_PXO_HAS_LEFT)], MULTI_PXO_HAS_LEFT)){
3901                 return 1;
3902         }
3903
3904         // check the end of the line
3905         return 0;
3906 }
3907
3908 // recalculate the chat start index, and adjust the slider properly
3909 void multi_pxo_chat_adjust_start()
3910 {
3911         chat_line *moveup;
3912
3913         // if we have no chat
3914         if (Multi_pxo_chat == NULL) {
3915                 Multi_pxo_chat_start_index = -1;                
3916                 return;
3917         }
3918
3919         // traverse
3920         Multi_pxo_chat_start_index = 0;
3921         moveup = Multi_pxo_chat;
3922         while((moveup != Multi_pxo_chat_start) && (moveup != NULL)){
3923                 Multi_pxo_chat_start_index++;
3924                 moveup = moveup->next;
3925         }
3926
3927         // set the slider index
3928         Multi_pxo_chat_slider.force_currentItem(Multi_pxo_chat_start_index);
3929 }
3930
3931 // motd stuff ---------------------------------------------------------
3932
3933 // initialize motd when going into this screen
3934 void multi_pxo_motd_init()
3935 {
3936         // zero the motd string
3937         SDL_strlcpy(Pxo_motd, "", SDL_arraysize(Pxo_motd));
3938
3939         // haven't gotten it yet
3940         Pxo_motd_end = 0;
3941
3942         // haven't read it yet either
3943         Pxo_motd_read = 0;
3944 }
3945
3946 // set the motd text
3947 void multi_pxo_motd_add_text(const char *text)
3948 {
3949         int cur_len = strlen(Pxo_motd);
3950         int new_len;
3951
3952         // sanity
3953         if(text == NULL){
3954                 return;
3955         }
3956
3957         // make sure its motd text
3958         SDL_assert(multi_pxo_is_motd_text(text));
3959         if(!multi_pxo_is_motd_text(text)){
3960                 return;
3961         }
3962         
3963         // if its a 0 line motd
3964         if(strlen(text) <= strlen(PXO_CHAT_MOTD_PREFIX)){
3965                 return;
3966         }
3967
3968         // add text to the motd
3969         new_len = strlen(text + strlen(PXO_CHAT_MOTD_PREFIX)) - 1;
3970         if((cur_len + new_len + 1) < MAX_PXO_MOTD_LEN){
3971                 SDL_strlcat(Pxo_motd, text + strlen(PXO_CHAT_MOTD_PREFIX) + 1, SDL_arraysize(Pxo_motd));
3972                 SDL_strlcat(Pxo_motd, "\n", SDL_arraysize(Pxo_motd));
3973                 mprintf(("MOTD ADD : %s\n", Pxo_motd));
3974         }
3975 }
3976
3977 // set end of motd
3978 void multi_pxo_set_end_of_motd()
3979 {
3980         int blink = 1;
3981
3982         Pxo_motd_end = 1;
3983         mprintf(("MOTD ALL : %s\n", Pxo_motd));
3984         
3985         Pxo_motd_read = 0;
3986
3987         // do we have an old MOTD file laying around? If so, read it in and see if its the same
3988         uint old_chksum;
3989         uint new_chksum;
3990
3991         // checksum the current motd            
3992         new_chksum = cf_add_chksum_long(0, Pxo_motd, strlen(Pxo_motd));         
3993
3994         // checksum the old motd if its lying around
3995         CFILE *in = cfopen("oldmotd.txt", "rb");
3996         if(in != NULL){
3997                 // read the old checksum
3998                 old_chksum = cfread_uint(in);
3999                 cfclose(in);
4000                 
4001                 // same checksum? no blink
4002                 if(new_chksum == old_chksum){
4003                         blink = 0;
4004                 }
4005         }       
4006         
4007         // write out the motd for next time
4008         if(strlen(Pxo_motd)){
4009                 CFILE *out = cfopen("oldmotd.txt", "wb", CFILE_NORMAL, CF_TYPE_DATA);
4010                 if(out != NULL){
4011                         // write all the text
4012                         cfwrite_uint(new_chksum, out);
4013                         
4014                         // close the outfile
4015                         cfclose(out);
4016                 }
4017         }
4018         
4019         // set the blink stamp
4020         Pxo_motd_blink_stamp = -1;
4021         if(blink){              
4022                 Pxo_motd_blink_on = 0;
4023                 if(!Pxo_motd_blinked_already){
4024                         Pxo_motd_blink_stamp = timestamp(PXO_MOTD_BLINK_TIME);
4025                         Pxo_motd_blink_on = 1;
4026                 }
4027         }
4028
4029         Pxo_motd_blinked_already = 1;
4030 }
4031
4032 // display the motd dialog
4033 void multi_pxo_motd_dialog()
4034 {
4035         // mark the motd as read
4036         Pxo_motd_read = 1;
4037
4038         // simple popup, with a slider
4039         popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, Pxo_motd);
4040 }
4041
4042 // call to maybe blink the motd button
4043 void multi_pxo_motd_maybe_blit()
4044 {
4045         // if we got the end of the motd, and he hasn't read it yet
4046         if(Pxo_motd_end && !Pxo_motd_read && (Pxo_motd_blink_stamp != -1)){
4047                 // if the timestamp elapsed, flip the blink flag
4048                 if(timestamp_elapsed(Pxo_motd_blink_stamp)){
4049                         Pxo_motd_blink_on = !Pxo_motd_blink_on;
4050                         Pxo_motd_blink_stamp = timestamp(PXO_MOTD_BLINK_TIME);
4051                 }
4052
4053                 // draw properly
4054                 if(Pxo_motd_blink_on){
4055                         Multi_pxo_buttons[gr_screen.res][MULTI_PXO_MOTD].button.draw_forced(2);
4056                 }
4057         }       
4058 }
4059
4060
4061 // common dialog stuff ------------------------------------------------
4062
4063 int Multi_pxo_searching = 0; 
4064
4065 // initialize the common dialog with the passed max input length
4066 void multi_pxo_com_init(int input_len)
4067 {
4068         int idx;
4069         
4070         // create the interface window
4071         Multi_pxo_com_window.create(0, 0, gr_screen.max_w,gr_screen.max_h, 0);
4072         Multi_pxo_com_window.set_mask_bmap(Multi_pxo_com_mask_fname[gr_screen.res]);    
4073
4074         // create the interface buttons
4075         for(idx=0; idx<MULTI_PXO_COM_NUM_BUTTONS; idx++){
4076                 // create the object
4077                 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);
4078
4079                 // set the sound to play when highlighted
4080                 Multi_pxo_com_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
4081
4082                 // set the ani for the button
4083                 Multi_pxo_com_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_com_buttons[gr_screen.res][idx].filename);
4084
4085                 // set the hotspot
4086                 Multi_pxo_com_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_com_buttons[gr_screen.res][idx].hotspot);
4087         }                       
4088
4089         // add xstrs
4090         for(idx=0; idx<MULTI_PXO_COM_NUM_TEXT; idx++){
4091                 Multi_pxo_com_window.add_XSTR(&Multi_pxo_com_text[gr_screen.res][idx]);
4092         }
4093
4094         // create the input box
4095         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);   
4096         Multi_pxo_com_input.set_focus();
4097
4098         // clear all text lines
4099         SDL_zero(Multi_pxo_com_bottom_text);
4100         SDL_zero(Multi_pxo_com_middle_text);
4101         SDL_zero(Multi_pxo_com_top_text);
4102 }
4103
4104 // close down the common dialog
4105 void multi_pxo_com_close()
4106 {
4107         // destroy the UI_WINDOW
4108         Multi_pxo_com_window.destroy();
4109 }
4110
4111 // blit all text lines, top, middle, bottoms
4112 void multi_pxo_com_blit_text()
4113 {
4114         // blit top, middle and bottom text if possible
4115         if(strlen(Multi_pxo_com_top_text) > 0){
4116                 gr_set_color_fast(&Color_bright);
4117                 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);
4118         }
4119         if(strlen(Multi_pxo_com_middle_text) > 0){
4120                 gr_set_color_fast(&Color_bright);
4121                 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);
4122         }
4123         if(strlen(Multi_pxo_com_bottom_text) > 0){
4124                 gr_set_color_fast(&Color_bright);
4125                 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);
4126         }
4127 }
4128
4129 // set the top text, shortening as necessary
4130 void multi_pxo_com_set_top_text(const char *txt)
4131 {       
4132         if((txt != NULL) && strlen(txt)){
4133                 SDL_strlcpy(Multi_pxo_com_top_text, txt, SDL_arraysize(Multi_pxo_com_top_text));
4134                 gr_force_fit_string(Multi_pxo_com_top_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4135         }       
4136 }
4137
4138 // set the middle text, shortening as necessary
4139 void multi_pxo_com_set_middle_text(const char *txt)
4140 {
4141         if((txt != NULL) && strlen(txt)){
4142                 SDL_strlcpy(Multi_pxo_com_middle_text, txt, SDL_arraysize(Multi_pxo_com_middle_text));
4143                 gr_force_fit_string(Multi_pxo_com_middle_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4144         }       
4145 }
4146
4147 // set the bottom text, shortening as necessary
4148 void multi_pxo_com_set_bottom_text(const char *txt)
4149 {
4150         if((txt != NULL) && strlen(txt)){
4151                 SDL_strlcpy(Multi_pxo_com_bottom_text, txt, SDL_arraysize(Multi_pxo_com_bottom_text));
4152                 gr_force_fit_string(Multi_pxo_com_bottom_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4153         }       
4154 }
4155
4156
4157 // private channel join stuff -----------------------------------------
4158
4159 // initialize the popup
4160 void multi_pxo_priv_init()
4161 {
4162         SDL_assert(Multi_pxo_mode != MULTI_PXO_MODE_PRIVATE);
4163
4164         // initialize the common dialog with the passed max input length
4165         multi_pxo_com_init(MULTI_PXO_PRIV_MAX_TEXT_LEN);
4166         
4167         // initialize the return code
4168         Multi_pxo_priv_return_code = -1;        
4169
4170         // mark us as running
4171         Multi_pxo_mode = MULTI_PXO_MODE_PRIVATE;
4172
4173         // set some text
4174         multi_pxo_com_set_middle_text(XSTR("Type the name of the channel to join/create",961)); 
4175 }
4176
4177 // close down the popup
4178 void multi_pxo_priv_close()
4179 {       
4180         // close down the common dialog
4181         multi_pxo_com_close();
4182
4183         // mark us as not running any more
4184         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
4185 }
4186
4187 // run the popup, 0 if still running, -1 if cancel, 1 if ok 
4188 int multi_pxo_priv_popup()
4189 {
4190         int k;
4191         
4192         // if we're not already running, initialize stuff
4193         if(Multi_pxo_mode != MULTI_PXO_MODE_PRIVATE){
4194                 // intialize
4195                 multi_pxo_priv_init();
4196
4197                 // return "still running"
4198                 return 0;
4199         }
4200
4201         k = Multi_pxo_com_window.process();
4202
4203         // process keypresses
4204         switch(k){
4205         // like hitting the cancel button
4206         case SDLK_ESCAPE:
4207                 Multi_pxo_priv_return_code = 0;
4208                 break;
4209         }
4210
4211         // process button presses
4212         multi_pxo_priv_process_buttons();
4213
4214         // process the inputbox
4215         multi_pxo_priv_process_input();
4216
4217         // blit the background
4218         multi_pxo_blit_all();   
4219
4220         // blit my stuff                
4221         gr_reset_clip();        
4222         gr_set_bitmap(Multi_pxo_com_bitmap);
4223         gr_bitmap(Multi_pxo_com_coords[gr_screen.res][0], Multi_pxo_com_coords[gr_screen.res][1]);
4224         Multi_pxo_com_window.draw();    
4225
4226         // blit all text lines, top, middle, bottoms
4227         multi_pxo_com_blit_text();
4228
4229         gr_flip();
4230
4231         // check the return code
4232         switch(Multi_pxo_priv_return_code){
4233         // still in progress
4234         case -1 :
4235                 return 0;
4236
4237         // user hit cancel
4238         case 0 :
4239                 multi_pxo_priv_close();
4240                 return -1;
4241
4242         // user hit ok
4243         case 1 :                
4244                 multi_pxo_priv_close();
4245                 return 1;
4246         }       
4247
4248         return 0;
4249 }
4250
4251 // process button presses
4252 void multi_pxo_priv_process_buttons()
4253 {
4254         int idx;
4255
4256         // check all buttons
4257         for(idx=0;idx<MULTI_PXO_COM_NUM_BUTTONS;idx++){
4258                 if(Multi_pxo_com_buttons[gr_screen.res][idx].button.pressed()){
4259                         multi_pxo_priv_button_pressed(idx);
4260                         return;
4261                 }
4262         }
4263 }
4264
4265 // handle a button press
4266 void multi_pxo_priv_button_pressed(int n)
4267 {
4268         char priv_chan_name[128];
4269
4270         switch(n){      
4271         case MULTI_PXO_COM_CANCEL:
4272                 Multi_pxo_priv_return_code = 0;
4273                 break;
4274         
4275         case MULTI_PXO_COM_OK:
4276                 Multi_pxo_com_input.get_text(priv_chan_name);
4277                 multi_pxo_strip_space(priv_chan_name, priv_chan_name, SDL_arraysize(priv_chan_name));
4278
4279                 // if its a 0 length string, interpret as a cancel
4280                 if(strlen(priv_chan_name) <= 0){
4281                         Multi_pxo_priv_return_code = 0;
4282                         return;
4283                 }
4284
4285                 Multi_pxo_priv_return_code = 1;
4286                 break;
4287         }       
4288 }
4289
4290 // process the inputbox
4291 void multi_pxo_priv_process_input()
4292 {
4293         char priv_chan_name[128];
4294         
4295         // see if the user has pressed enter
4296         if(Multi_pxo_com_input.pressed()){
4297                 Multi_pxo_com_input.get_text(priv_chan_name);
4298                 multi_pxo_strip_space(priv_chan_name, priv_chan_name, SDL_arraysize(priv_chan_name));
4299                 
4300                 // if its a 0 length string, interpret as a cancel
4301                 if(strlen(priv_chan_name) <= 0){
4302                         Multi_pxo_priv_return_code = 0;
4303                         return;
4304                 }
4305
4306                 // otherwise interpret as "accept"
4307                 Multi_pxo_priv_return_code = 1;
4308
4309                 // add in the "+" which indicates a private room
4310                 SDL_strlcpy(Multi_pxo_priv_chan, "+", SDL_arraysize(Multi_pxo_priv_chan));
4311                 SDL_strlcat(Multi_pxo_priv_chan, priv_chan_name, SDL_arraysize(Multi_pxo_priv_chan));
4312         }
4313 }
4314
4315 // find player stuff -----------------------------------------
4316
4317 char name_lookup[255];
4318
4319 // initialize the popup
4320 void multi_pxo_find_init()
4321 {
4322         SDL_assert(Multi_pxo_mode != MULTI_PXO_MODE_FIND);
4323
4324         // initialize the common dialog with the passed max input length
4325         multi_pxo_com_init(MAX_PLAYER_NAME_LEN);        
4326
4327         // return code, set to something other than -1 if we're supposed to return
4328         Multi_pxo_find_return_code = -1;
4329
4330         // mark us as running
4331         Multi_pxo_mode = MULTI_PXO_MODE_FIND;   
4332
4333         // not searching yet
4334         Multi_pxo_searching = 0; 
4335
4336         // set the top text
4337         multi_pxo_com_set_top_text(XSTR("Enter user to be found",962)); 
4338
4339         // 0 length
4340         SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4341
4342         // 0 length
4343         SDL_strlcpy(name_lookup, "", SDL_arraysize(name_lookup));
4344 }
4345
4346 // close down the popup
4347 void multi_pxo_find_close()
4348 {
4349         // close down the common dialog
4350         multi_pxo_com_close();
4351
4352         // mark us as not running any more
4353         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
4354 }
4355
4356 // run the popup, 0 if still running, -1 if cancel, 1 if ok 
4357 int multi_pxo_find_popup()
4358 {
4359         int k;
4360         
4361         // if we're not already running, initialize stuff
4362         if(Multi_pxo_mode != MULTI_PXO_MODE_FIND){
4363                 // intialize
4364                 multi_pxo_find_init();
4365
4366                 // return "still running"
4367                 return 0;
4368         }
4369
4370         k = Multi_pxo_com_window.process();
4371
4372         // process keypresses
4373         switch(k){
4374         // like hitting the cancel button
4375         case SDLK_ESCAPE:
4376                 Multi_pxo_find_return_code = 0;
4377                 break;
4378         }
4379
4380         // process button presses
4381         multi_pxo_find_process_buttons();
4382
4383         // process the inputbox
4384         multi_pxo_find_process_input();
4385
4386         // process search mode if applicable
4387         multi_pxo_find_search_process();
4388
4389         // blit the background
4390         multi_pxo_blit_all();   
4391
4392         // blit my stuff                
4393         gr_reset_clip();        
4394         gr_set_bitmap(Multi_pxo_com_bitmap);
4395         gr_bitmap(Multi_pxo_com_coords[gr_screen.res][0], Multi_pxo_com_coords[gr_screen.res][1]);
4396         Multi_pxo_com_window.draw();    
4397
4398         // blit any text lines
4399         multi_pxo_com_blit_text();
4400         
4401         gr_flip();
4402
4403         // check the return code
4404         switch(Multi_pxo_find_return_code){
4405         // still in progress
4406         case -1 :
4407                 return 0;
4408
4409         // user hit cancel
4410         case 0 :
4411                 // close the popup down
4412                 multi_pxo_find_close();
4413                 return -1;
4414
4415         // user hit ok
4416         case 1 :                
4417                 // close the popup down
4418                 multi_pxo_find_close();
4419
4420                 // if we have a channel, join it now if possible
4421                 if(strlen(Multi_pxo_find_channel) > 0){
4422                         pxo_channel *lookup;
4423                         lookup = multi_pxo_find_channel(Multi_pxo_find_channel,Multi_pxo_channels);
4424                         
4425                         // if we couldn't find it, don't join
4426                         if(lookup != NULL){                             
4427                                 multi_pxo_join_channel(lookup);
4428                         }
4429                 }
4430                 return 1;
4431         }       
4432
4433         return 0;
4434 }
4435
4436 // process button presses
4437 void multi_pxo_find_process_buttons()
4438 {
4439         int idx;
4440
4441         // check all buttons
4442         for(idx=0;idx<MULTI_PXO_COM_NUM_BUTTONS;idx++){
4443                 if(Multi_pxo_com_buttons[gr_screen.res][idx].button.pressed()){
4444                         multi_pxo_find_button_pressed(idx);
4445                         return;
4446                 }
4447         }
4448 }
4449
4450 // handle a button press
4451 void multi_pxo_find_button_pressed(int n)
4452 {
4453         switch(n){      
4454         case MULTI_PXO_COM_CANCEL:
4455                 Multi_pxo_find_return_code = 0;
4456                 break;
4457         
4458         case MULTI_PXO_COM_OK:
4459                 Multi_pxo_find_return_code = 1;
4460                 break;
4461         }       
4462 }
4463
4464 // process the inputbox
4465 void multi_pxo_find_process_input()
4466 {               
4467         // see if the user has pressed enter
4468         if(Multi_pxo_com_input.pressed()){
4469                 // if we're not already in search mode
4470                 if(!Multi_pxo_searching){
4471                         // clear all text
4472                         SDL_zero(Multi_pxo_com_middle_text);
4473                         SDL_zero(Multi_pxo_com_bottom_text);
4474
4475                         Multi_pxo_com_input.get_text(name_lookup);
4476                         multi_pxo_strip_space(name_lookup, name_lookup, SDL_arraysize(name_lookup));
4477
4478                         // never search with a zero length string
4479                         if(strlen(name_lookup) > 0){
4480                                 char search_text[512];
4481
4482                                 // put us in search mode
4483                                 Multi_pxo_searching = 1;
4484
4485                                 // look for the guy
4486                                 GetChannelByUser(name_lookup);                  
4487
4488                                 // set the top text
4489                                 SDL_snprintf(search_text, SDL_arraysize(search_text), XSTR("Searching for %s", 963), name_lookup);
4490                                 multi_pxo_com_set_top_text(search_text);
4491                         }
4492                         // clear everything
4493                         else {
4494                                 SDL_zero(Multi_pxo_com_top_text);
4495                         }
4496                 }
4497         }
4498 }
4499
4500 // process search mode if applicable
4501 void multi_pxo_find_search_process()
4502 {
4503         char *channel;
4504         
4505         // if we're not searching for anything, return
4506         if(!Multi_pxo_searching){
4507                 return;
4508         }
4509
4510         // otherwise check to see if we've found him
4511         channel = GetChannelByUser(NULL);
4512         
4513         // if we've got a result, let the user know
4514         if(channel){
4515                 // if he couldn't be found
4516                 if(channel == (char *)-1){
4517                         multi_pxo_com_set_middle_text(XSTR("User not found",964));                                                                      
4518                         SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4519                 } else {        
4520                         if(channel[0] == '*'){
4521                                 multi_pxo_com_set_middle_text(XSTR("Player is logged in but is not on a channel",965));                         
4522                                 SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4523                         } else {
4524                                 char p_text[512];
4525
4526                                 // if this guy is on a public channel, display which one
4527                                 if(channel[0] == '#'){                  
4528                                         SDL_snprintf(p_text, SDL_arraysize(p_text), XSTR("Found %s on :", 966), name_lookup);
4529
4530                                         // display the results                                                          
4531                                         multi_pxo_com_set_middle_text(p_text);                                                          
4532                                         multi_pxo_com_set_bottom_text(channel+1);
4533
4534                                         // mark down the channel name so we know where to find him
4535                                         SDL_strlcpy(Multi_pxo_find_channel, channel, SDL_arraysize(Multi_pxo_find_channel));
4536                                         // strip out trailing whitespace
4537                                         if(Multi_pxo_find_channel[strlen(Multi_pxo_find_channel) - 1] == ' '){
4538                                                 Multi_pxo_find_channel[strlen(Multi_pxo_find_channel) - 1] = '\0';
4539                                         }                               
4540                                 }
4541                                 // if this is a private channel
4542                                 else if(channel[0] == '+'){
4543                                         SDL_snprintf(p_text, SDL_arraysize(p_text), XSTR("Found %s on a private channel", 967), name_lookup);
4544                                         multi_pxo_com_set_middle_text(p_text);
4545
4546                                         SDL_strlcpy(Multi_pxo_find_channel, "", SDL_arraysize(Multi_pxo_find_channel));
4547                                 }                                                               
4548                         }
4549                 }
4550
4551                 // unset search mode
4552                 Multi_pxo_searching = 0;
4553
4554                 // clear the inputbox
4555                 Multi_pxo_com_input.set_text("");
4556         }
4557 }
4558
4559
4560 // player info stuff -----------------------------------------
4561
4562 // popup conditional functions, returns 10 on successful get of stats
4563 int multi_pxo_pinfo_cond()
4564 {
4565         char *ret_string;
4566         char temp_string[255];
4567         char *tok;
4568
4569         // process common stuff
4570         multi_pxo_process_common();
4571
4572         // run the networking functions for the PXO API
4573         multi_pxo_api_process();
4574
4575         // process depending on what mode we're in
4576         switch(Multi_pxo_retrieve_mode){        
4577         // getting his player tracker id
4578         case 0:         
4579                 // if the thing is non-null, do something               
4580                 ret_string = GetTrackerIdByUser(Multi_pxo_retrieve_name);
4581                 if(ret_string != NULL){
4582                         // user not-online/not found
4583                         if(ret_string == (char *)-1){
4584                                 return 1;
4585                         } 
4586
4587                         // user not a tracker pilot
4588                         if(!SDL_strcasecmp(ret_string,"-1")){
4589                                 return 1;
4590                         }
4591
4592                         // otherwise parse into his id and callsign
4593                         SDL_strlcpy(temp_string, ret_string, SDL_arraysize(temp_string));
4594                         tok = strtok(temp_string," ");
4595                         
4596                         // get tracker id
4597                         if(tok != NULL){
4598                                 SDL_strlcpy(Multi_pxo_retrieve_id, tok, SDL_arraysize(Multi_pxo_retrieve_id));
4599
4600                                 // get the callsign
4601                                 tok = strtok(NULL,"");
4602                                 if(tok != NULL){
4603                                         SDL_strlcpy(Multi_pxo_retrieve_name, tok, SDL_arraysize(Multi_pxo_retrieve_name));
4604                                 }
4605                                 // failure
4606                                 else {
4607                                         return 1;
4608                                 }
4609                         }
4610                         // failure of some kind or another
4611                         else {
4612                                 return 1;
4613                         }                       
4614
4615                         Multi_pxo_retrieve_mode = 1;
4616                         return 0;                       
4617                 }
4618                 break;
4619
4620         // initial call to get his stats
4621         case 1:         
4622                 // change the popup text
4623                 popup_change_text(XSTR("Getting player stats",968));
4624
4625                 // fill in the data
4626                 memset(&Multi_pxo_pinfo, 0, sizeof(vmt_freespace2_struct));
4627                 SDL_strlcpy(Multi_pxo_pinfo.pilot_name, Multi_pxo_retrieve_name, SDL_arraysize(Multi_pxo_pinfo.pilot_name));
4628                 SDL_strlcpy(Multi_pxo_pinfo.tracker_id, Multi_pxo_retrieve_id, SDL_arraysize(Multi_pxo_pinfo.tracker_id));
4629
4630                 // make the initial call to the API
4631                 GetFSPilotData((vmt_freespace2_struct*)0xffffffff,NULL,NULL,0);
4632                 if(GetFSPilotData(&Multi_pxo_pinfo,Multi_pxo_retrieve_name,Multi_pxo_retrieve_id,0) != 0){
4633                         return 2;
4634                 }
4635                 // if the call went through, set the mode to 2
4636                 else {
4637                         Multi_pxo_retrieve_mode = 2;
4638                 }
4639                 break;
4640         
4641         // busy retrieving his stats
4642         case 2:
4643                 switch(GetFSPilotData(NULL,NULL,NULL,0)){
4644                 // timeout, fail, cancel
4645                 case -1:
4646                 case 3:
4647                 case 2:
4648                         return 2;                       
4649
4650                 // got the data
4651                 case 1:
4652                         return 10;
4653
4654                 // still busy
4655                 case 0:
4656                         break;
4657                 }
4658
4659                 break;          
4660         }
4661
4662         // return not done yet
4663         return 0;
4664 }
4665
4666 // return 1 if Multi_pxo_pinfo was successfully filled in, 0 otherwise
4667 int multi_pxo_pinfo_get(char *name)
4668 {
4669         // run the popup        
4670         Multi_pxo_retrieve_mode = 0;
4671         SDL_strlcpy(Multi_pxo_retrieve_name, name, SDL_arraysize(Multi_pxo_retrieve_name));
4672         switch(popup_till_condition(multi_pxo_pinfo_cond,XSTR("&Cancel", 779),XSTR("Retrieving player tracker id",969))){
4673         // success
4674         case 10 :
4675                 return 1;               
4676
4677         // failed to get his tracker id
4678         case 1 :
4679                 return 0;
4680
4681         // failed to get his stats
4682         case 2 :
4683                 return 0;       
4684         }
4685                         
4686         // we didn't get the stats
4687         return 0;
4688 }
4689
4690 // fire up the stats view popup
4691 void multi_pxo_pinfo_show()
4692 {
4693         // initialize the popup
4694         multi_pxo_pinfo_init();
4695         
4696         // run the popup
4697         do {
4698                 game_set_frametime(GS_STATE_PXO);
4699         } while(!multi_pxo_pinfo_do());
4700
4701         // close down the popup
4702         multi_pxo_pinfo_close();
4703 }
4704
4705 // build the stats labels values
4706 void multi_pxo_pinfo_build_vals()
4707 {
4708         vmt_freespace2_struct *fs = &Multi_pxo_pinfo;   
4709
4710         SDL_zero(Multi_pxo_pinfo_vals);
4711
4712         // pilot name
4713         SDL_strlcpy(Multi_pxo_pinfo_vals[0], fs->pilot_name, SDL_arraysize(Multi_pxo_pinfo_vals[0]));
4714         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]));
4715
4716         // rank
4717         multi_sg_rank_build_name(Ranks[fs->rank].name, Multi_pxo_pinfo_vals[1], SDL_arraysize(Multi_pxo_pinfo_vals[1]));
4718         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]));
4719
4720         // kills
4721         SDL_snprintf(Multi_pxo_pinfo_vals[2], SDL_arraysize(Multi_pxo_pinfo_vals[2]), "%d", fs->kill_count);
4722
4723         // assists
4724         SDL_snprintf(Multi_pxo_pinfo_vals[3], SDL_arraysize(Multi_pxo_pinfo_vals[3]), "%d", fs->assists);
4725
4726         // friendly kills
4727         SDL_snprintf(Multi_pxo_pinfo_vals[4], SDL_arraysize(Multi_pxo_pinfo_vals[4]), "%d", fs->kill_count - fs->kill_count_ok);
4728
4729         // missions flown
4730         SDL_snprintf(Multi_pxo_pinfo_vals[5], SDL_arraysize(Multi_pxo_pinfo_vals[5]), "%d", (int)fs->missions_flown);
4731
4732         // flight time  
4733         game_format_time(fl2f((float)fs->flight_time), Multi_pxo_pinfo_vals[6], SDL_arraysize(Multi_pxo_pinfo_vals[6]));
4734
4735         // last flown
4736         if(fs->last_flown == 0){                
4737                 SDL_strlcpy(Multi_pxo_pinfo_vals[7], XSTR("No missions flown", 970), SDL_arraysize(Multi_pxo_pinfo_vals[7]));
4738         } else {
4739                 tm *tmr = gmtime((time_t*)&fs->last_flown);
4740                 if(tmr != NULL){
4741                         strftime(Multi_pxo_pinfo_vals[7],30,"%m/%d/%y %H:%M",tmr);      
4742                 } else {
4743                         SDL_strlcpy(Multi_pxo_pinfo_vals[7], "", SDL_arraysize(Multi_pxo_pinfo_vals[7]));
4744                 }
4745         }               
4746
4747         // primary shots fired
4748         SDL_snprintf(Multi_pxo_pinfo_vals[8], SDL_arraysize(Multi_pxo_pinfo_vals[8]), "%d", (int)fs->p_shots_fired);
4749
4750         // primary shots hit
4751         SDL_snprintf(Multi_pxo_pinfo_vals[9], SDL_arraysize(Multi_pxo_pinfo_vals[9]), "%d", (int)fs->p_shots_hit);
4752
4753         // primary hit pct
4754         if(fs->p_shots_fired > 0){              
4755                 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));
4756         } else {                
4757                 SDL_strlcpy(Multi_pxo_pinfo_vals[10], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[10]));
4758         }
4759
4760         // secondary shots fired
4761         SDL_snprintf(Multi_pxo_pinfo_vals[11], SDL_arraysize(Multi_pxo_pinfo_vals[11]), "%d", (int)fs->s_shots_fired);
4762
4763         // secondary shots hit
4764         SDL_snprintf(Multi_pxo_pinfo_vals[12], SDL_arraysize(Multi_pxo_pinfo_vals[12]), "%d", (int)fs->s_shots_hit);
4765
4766         // secondary hit pct
4767         if(fs->s_shots_fired > 0){              
4768                 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));
4769         } else {                
4770                 SDL_strlcpy(Multi_pxo_pinfo_vals[13], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[13]));
4771         }
4772
4773         // primary friendly hits
4774         SDL_snprintf(Multi_pxo_pinfo_vals[14], SDL_arraysize(Multi_pxo_pinfo_vals[14]), "%d", fs->p_bonehead_hits);
4775
4776         // primary friendly hit %
4777         if(fs->p_shots_fired > 0){
4778                 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)));
4779         } else {                
4780                 SDL_strlcpy(Multi_pxo_pinfo_vals[15], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[15]));
4781         }
4782
4783         // secondary friendly hits
4784         SDL_snprintf(Multi_pxo_pinfo_vals[16], SDL_arraysize(Multi_pxo_pinfo_vals[16]), "%d", fs->s_bonehead_hits);
4785
4786         // secondary friendly hit %
4787         if(fs->s_shots_fired > 0){
4788                 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)));
4789         } else {                
4790                 SDL_strlcpy(Multi_pxo_pinfo_vals[17], "0%", SDL_arraysize(Multi_pxo_pinfo_vals[17]));
4791         }
4792 }
4793
4794 // initialize the popup
4795 void multi_pxo_pinfo_init()
4796 {
4797         int idx;
4798         
4799         // create the interface window
4800         Multi_pxo_pinfo_window.create(0,0,gr_screen.max_w,gr_screen.max_h,0);
4801         Multi_pxo_pinfo_window.set_mask_bmap(Multi_pxo_pinfo_mask_fname[gr_screen.res]);        
4802         
4803         Multi_pxo_pinfo_bitmap = bm_load(Multi_pxo_pinfo_fname[gr_screen.res]);
4804         SDL_assert(Multi_pxo_pinfo_bitmap != -1);
4805
4806         // create the interface buttons
4807         for(idx=0; idx<MULTI_PXO_PINFO_NUM_BUTTONS; idx++){
4808                 // create the object
4809                 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);
4810
4811                 // set the sound to play when highlighted
4812                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
4813
4814                 // set the ani for the button
4815                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_pinfo_buttons[gr_screen.res][idx].filename);
4816
4817                 // set the hotspot
4818                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_pinfo_buttons[gr_screen.res][idx].hotspot);
4819         }                               
4820
4821         // add xstrs
4822         for(idx=0; idx<MULTI_PXO_PINFO_NUM_TEXT; idx++){
4823                 Multi_pxo_pinfo_window.add_XSTR(&Multi_pxo_pinfo_text[gr_screen.res][idx]);
4824         }
4825
4826         // set up the stats labels
4827         Multi_pxo_pinfo_stats_labels[0] = strdup(XSTR("Name", 1532));
4828         Multi_pxo_pinfo_stats_labels[1] = strdup(XSTR("Rank", 1533));
4829         Multi_pxo_pinfo_stats_labels[2] = strdup(XSTR("Kills", 1534));
4830         Multi_pxo_pinfo_stats_labels[3] = strdup(XSTR("Assists", 1535));
4831         Multi_pxo_pinfo_stats_labels[4] = strdup(XSTR("Friendly kills", 1536));
4832         Multi_pxo_pinfo_stats_labels[5] = strdup(XSTR("Missions flown", 1537));
4833         Multi_pxo_pinfo_stats_labels[6] = strdup(XSTR("Flight time", 1538));
4834         Multi_pxo_pinfo_stats_labels[7] = strdup(XSTR("Last flown", 1539));
4835         Multi_pxo_pinfo_stats_labels[8] = strdup(XSTR("Primary shots fired", 1540));
4836         Multi_pxo_pinfo_stats_labels[9] = strdup(XSTR("Primary shots hit", 1541));
4837         Multi_pxo_pinfo_stats_labels[10] = strdup(XSTR("Primary hit %", 1542));
4838         Multi_pxo_pinfo_stats_labels[11] = strdup(XSTR("Secondary shots fired", 1543));
4839         Multi_pxo_pinfo_stats_labels[12] = strdup(XSTR("Secondary shots hit", 1544));
4840         Multi_pxo_pinfo_stats_labels[13] = strdup(XSTR("Secondary hit %", 1545));
4841         Multi_pxo_pinfo_stats_labels[14] = strdup(XSTR("Primary friendly hits", 1546));
4842         Multi_pxo_pinfo_stats_labels[15] = strdup(XSTR("Primary friendly hit %", 1547));
4843         Multi_pxo_pinfo_stats_labels[16] = strdup(XSTR("Secondary friendly hits", 1548));
4844         Multi_pxo_pinfo_stats_labels[17] = strdup(XSTR("Secondary friendly hit %", 1549));
4845
4846         // build the stats labels values
4847         multi_pxo_pinfo_build_vals();
4848 }
4849
4850 // do frame
4851 int multi_pxo_pinfo_do()
4852 {
4853         int k = Multi_pxo_pinfo_window.process();
4854
4855         // process common stuff
4856         multi_pxo_process_common();
4857
4858         // run the networking functions for the PXO API
4859         multi_pxo_api_process();
4860
4861         // check to see if he pressed escp
4862         if(k == SDLK_ESCAPE){
4863                 return 1;
4864         }
4865
4866         // if he pressed the ok button
4867         if(Multi_pxo_pinfo_buttons[gr_screen.res][MULTI_PXO_PINFO_OK].button.pressed()){
4868                 return 1;
4869         }
4870
4871         // if he pressed the medals buttons, run the medals screen
4872         if(Multi_pxo_pinfo_buttons[gr_screen.res][MULTI_PXO_PINFO_MEDALS].button.pressed()){
4873 #ifdef FS2_DEMO
4874         game_feature_not_in_demo_popup();
4875 #else
4876                 multi_pxo_run_medals();
4877 #endif
4878         }
4879         
4880         // draw stuff
4881
4882         // blit everything on the "normal" screen
4883         multi_pxo_blit_all();
4884
4885         // blit our own stuff
4886         gr_reset_clip();        
4887         gr_set_bitmap(Multi_pxo_pinfo_bitmap);
4888         gr_bitmap(0, 0);
4889         Multi_pxo_pinfo_window.draw();  
4890
4891         // blit the stats themselves
4892         multi_pxo_pinfo_blit();
4893
4894         // flip the page
4895         gr_flip();
4896
4897         // not done yet
4898         return 0;
4899 }
4900
4901 // close
4902 void multi_pxo_pinfo_close()
4903 {
4904         int i;
4905
4906         // destroy the UI_WINDOW
4907         Multi_pxo_pinfo_window.destroy();
4908
4909         // unload the bitmap
4910         if(Multi_pxo_pinfo_bitmap != -1){
4911                 bm_unload(Multi_pxo_pinfo_bitmap);
4912         }
4913
4914         // free the stats labels strings
4915         for (i=0; i<MULTI_PXO_PINFO_NUM_LABELS; i++) {
4916                 free(Multi_pxo_pinfo_stats_labels[i]);
4917         }
4918 }
4919
4920 // blit all the stats on this screen
4921 void multi_pxo_pinfo_blit()
4922 {
4923         int idx;
4924         int y_start;
4925         
4926         // blit all the labels  
4927         y_start = Multi_pxo_pinfo_coords[gr_screen.res][1];
4928         for(idx=0; idx<MULTI_PXO_PINFO_NUM_LABELS; idx++){
4929                 // blit the label
4930                 gr_set_color_fast(&Color_bright);
4931                 gr_string(Multi_pxo_pinfo_coords[gr_screen.res][0], y_start, Multi_pxo_pinfo_stats_labels[idx]);
4932
4933                 // blit the label's value
4934                 gr_set_color_fast(&Color_normal);
4935                 gr_string(Multi_pxo_pinfo_val_x[gr_screen.res], y_start, Multi_pxo_pinfo_vals[idx]);
4936
4937                 // spacing
4938                 y_start += Multi_pxo_pinfo_stats_spacing[idx];
4939         }
4940 }
4941
4942 // run the medals screen
4943 void multi_pxo_run_medals()
4944 {       
4945         int ret_code;
4946         
4947         // process common stuff
4948         multi_pxo_process_common();
4949
4950         // run the networking functions for the PXO API
4951         multi_pxo_api_process();
4952
4953         // initialize the freespace data and the player struct  
4954         multi_stats_tracker_to_fs(&Multi_pxo_pinfo, &Multi_pxo_pinfo_player.stats);
4955         SDL_strlcpy(Multi_pxo_pinfo_player.callsign, Multi_pxo_pinfo.pilot_name, SDL_arraysize(Multi_pxo_pinfo_player.callsign));
4956         
4957         // initialize the medals screen
4958         medal_main_init(&Multi_pxo_pinfo_player, MM_POPUP);
4959
4960         // run the medals screen until it says that it should be closed
4961         do {
4962                 // set frametime and run common functions
4963                 game_set_frametime(-1);
4964                 game_do_state_common(gameseq_get_state());
4965
4966                 // run the medals screen
4967                 ret_code = medal_main_do();             
4968         } while(ret_code);
4969
4970         // close the medals screen down
4971         medal_main_close();
4972         
4973         // reset the palette
4974         multi_pxo_load_palette();
4975 }
4976
4977
4978 // notify stuff stuff -----------------------------------------
4979
4980 // add a notification string
4981 void multi_pxo_notify_add(const char *txt)
4982 {
4983         // copy the text
4984         SDL_strlcpy(Multi_pxo_notify_text, txt, SDL_arraysize(Multi_pxo_notify_text));
4985
4986         // set the timestamp
4987         Multi_pxo_notify_stamp = timestamp(MULTI_PXO_NOTIFY_TIME);
4988 }
4989
4990 // blit and process the notification string
4991 void multi_pxo_notify_blit()
4992 {
4993         int w;
4994
4995         // if the timestamp is -1, do nothing
4996         if(Multi_pxo_notify_stamp == -1){
4997                 return;
4998         }
4999
5000         // if it has expired, do nothing
5001         if(timestamp_elapsed(Multi_pxo_notify_stamp)){
5002                 Multi_pxo_notify_stamp = -1;
5003         }
5004
5005         // otherwise blit the text
5006         gr_set_color_fast(&Color_bright);
5007         gr_get_string_size(&w,NULL,Multi_pxo_notify_text);
5008         gr_string((gr_screen.max_w - w)/2,MULTI_PXO_NOTIFY_Y,Multi_pxo_notify_text);
5009 }
5010
5011
5012 // initialize the PXO help screen
5013 void multi_pxo_help_init()
5014 {
5015         int idx;
5016         
5017         // load the background bitmap
5018         Multi_pxo_help_bitmap = bm_load(Multi_pxo_help_fname[gr_screen.res]);
5019         if(Multi_pxo_help_bitmap < 0){
5020                 // we failed to load the bitmap - this is very bad
5021                 Int3();
5022         }
5023                 // create the interface window
5024         Multi_pxo_help_window.create(0,0,gr_screen.max_w,gr_screen.max_h,0);
5025         Multi_pxo_help_window.set_mask_bmap(Multi_pxo_help_mask_fname[gr_screen.res]);
5026
5027                 // create the interface buttons
5028         for(idx=0; idx<MULTI_PXO_HELP_NUM_BUTTONS; idx++){
5029                 // create the object
5030                 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);
5031
5032                 // set the sound to play when highlighted
5033                 Multi_pxo_help_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
5034
5035                 // set the ani for the button
5036                 Multi_pxo_help_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_help_buttons[gr_screen.res][idx].filename);
5037
5038                 // set the hotspot
5039                 Multi_pxo_help_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_help_buttons[gr_screen.res][idx].hotspot);
5040         }       
5041         
5042         // add xstrs
5043         for(idx=0; idx<MULTI_PXO_HELP_NUM_TEXT; idx++){
5044                 Multi_pxo_help_window.add_XSTR(&Multi_pxo_help_text[gr_screen.res][idx]);
5045         }
5046
5047         // if we haven't already loaded in the text, do so
5048         // if(!Multi_pxo_help_loaded){
5049                 multi_pxo_help_load();
5050         // }
5051
5052         // set the current page to 0
5053         Multi_pxo_help_cur = 0;
5054 }
5055
5056 // do frame for PXO help
5057 void multi_pxo_help_do()
5058 {
5059         // run api stuff        
5060         if(Multi_pxo_connected){
5061                 multi_pxo_api_process();
5062         }
5063
5064         // process common stuff
5065         multi_pxo_process_common();
5066
5067         int k = Multi_pxo_help_window.process();
5068
5069         // process any keypresses
5070         switch(k){
5071         case SDLK_ESCAPE:
5072                 gamesnd_play_iface(SND_USER_SELECT);
5073                 gameseq_post_event(GS_EVENT_PXO);
5074                 break;
5075         }               
5076
5077         // process button presses
5078         multi_pxo_help_process_buttons();
5079
5080         // draw the background, etc
5081         gr_reset_clip();
5082         GR_MAYBE_CLEAR_RES(Multi_pxo_help_bitmap);
5083         if(Multi_pxo_help_bitmap != -1){
5084                 gr_set_bitmap(Multi_pxo_help_bitmap);
5085                 gr_bitmap(0,0);
5086         }
5087         Multi_pxo_help_window.draw();
5088
5089         // blit the current page
5090         multi_pxo_help_blit_page();
5091
5092         // page flip
5093         gr_flip();
5094 }
5095
5096 // close the pxo screen
5097 void multi_pxo_help_close()
5098 {
5099         int idx, idx2;
5100
5101         // unload any bitmaps
5102         bm_unload(Multi_pxo_help_bitmap);               
5103         
5104         // destroy the UI_WINDOW
5105         Multi_pxo_help_window.destroy();
5106
5107         // free all pages
5108         for(idx=0; idx<Multi_pxo_help_num_pages; idx++){
5109                 for(idx2=0; idx2<Multi_pxo_help_pages[idx].num_lines; idx2++){
5110                         // maybe free
5111                         if(Multi_pxo_help_pages[idx].text[idx2] != NULL){
5112                                 free(Multi_pxo_help_pages[idx].text[idx2]);
5113                                 Multi_pxo_help_pages[idx].text[idx2] = NULL;
5114                         }
5115                 }
5116         }
5117 }
5118
5119 // load the help file up
5120 void multi_pxo_help_load()
5121 {
5122         CFILE *in;      
5123         help_page *cp;  
5124         
5125         // if its already loaded, do nothing
5126         // if(Multi_pxo_help_loaded){
5127                 // return;
5128         // }
5129
5130         // read in the text file
5131         in = NULL;
5132         in = cfopen(MULTI_PXO_HELP_FILE,"rt",CFILE_NORMAL,CF_TYPE_DATA);                        
5133         SDL_assert(in != NULL);
5134         if(in == NULL){
5135                 return;
5136         }
5137
5138         Multi_pxo_help_num_pages = 0;
5139
5140         // blast all the help pages clear
5141         memset(Multi_pxo_help_pages, 0, sizeof(help_page) * MULTI_PXO_MAX_PAGES);       
5142         Multi_pxo_help_num_pages = 0;
5143         cp = &Multi_pxo_help_pages[0];
5144
5145         while(!cfeof(in)){
5146                 // malloc the line
5147                 cp->text[cp->num_lines] = (char*)malloc(Multi_pxo_chars_per_line[gr_screen.res]);
5148                 if(cp->text[cp->num_lines] == NULL){
5149                         break;
5150                 }
5151                 
5152                 // read in the next line                
5153                 cfgets(cp->text[cp->num_lines++], Multi_pxo_chars_per_line[gr_screen.res], in);
5154
5155                 // skip to the next page if necessary
5156                 if(cp->num_lines == Multi_pxo_lines_pp[gr_screen.res]){                 
5157                         Multi_pxo_help_num_pages++;
5158                         SDL_assert(Multi_pxo_help_num_pages < MULTI_PXO_MAX_PAGES);
5159                         if(Multi_pxo_help_num_pages >= MULTI_PXO_MAX_PAGES){
5160                                 Multi_pxo_help_num_pages--;
5161                                 break;
5162                         }
5163                         cp = &Multi_pxo_help_pages[Multi_pxo_help_num_pages];
5164                 }
5165         }
5166
5167         // close the file
5168         cfclose(in);
5169
5170         // mark the help as having been loaded
5171         // Multi_pxo_help_loaded = 1;
5172 }
5173
5174 // blit the current page
5175 void multi_pxo_help_blit_page()
5176 {
5177         int idx;
5178         int start_pos;
5179         int y_start;
5180         help_page *cp = &Multi_pxo_help_pages[Multi_pxo_help_cur];
5181         
5182         // blit each line
5183         y_start = Multi_pxo_help_coords[gr_screen.res][1];
5184         for(idx=0;idx<cp->num_lines;idx++){
5185                 // if the first symbol is "@", highlight the line
5186                 if(cp->text[idx][0] == '@'){
5187                         gr_set_color_fast(&Color_bright);
5188                         start_pos = 1;
5189                 } else {
5190                         gr_set_color_fast(&Color_normal);
5191                         start_pos = 0;
5192                 }
5193
5194                 // blit the line
5195                 gr_string(Multi_pxo_help_coords[gr_screen.res][0], y_start, cp->text[idx] + start_pos);
5196
5197                 // increment the y location
5198                 y_start += 10;
5199         }
5200 }
5201
5202 // process button presses
5203 void multi_pxo_help_process_buttons()
5204 {
5205         int idx;
5206
5207         // process all buttons
5208         for(idx=0;idx<MULTI_PXO_HELP_NUM_BUTTONS;idx++){
5209                 if(Multi_pxo_help_buttons[gr_screen.res][idx].button.pressed()){
5210                         multi_pxo_help_button_pressed(idx);
5211                         return;
5212                 }
5213         }
5214 }
5215
5216 // button pressed
5217 void multi_pxo_help_button_pressed(int n)
5218 {       
5219         switch(n){
5220         case MULTI_PXO_HELP_PREV:
5221                 // if we're already at page 0, do nothing
5222                 if(Multi_pxo_help_cur == 0){
5223                         gamesnd_play_iface(SND_GENERAL_FAIL);                   
5224                 } else {
5225                         Multi_pxo_help_cur--;
5226                         gamesnd_play_iface(SND_USER_SELECT);
5227                 }
5228                 break;
5229
5230         case MULTI_PXO_HELP_NEXT:
5231                 // if we're already at max pages, do nothing
5232                 if(Multi_pxo_help_cur == Multi_pxo_help_num_pages){
5233                         gamesnd_play_iface(SND_GENERAL_FAIL);
5234                 } else {
5235                         Multi_pxo_help_cur++;
5236                         gamesnd_play_iface(SND_USER_SELECT);
5237                 }
5238                 break;
5239
5240         case MULTI_PXO_HELP_CONTINUE:
5241                 gamesnd_play_iface(SND_USER_SELECT);
5242                 gameseq_post_event(GS_EVENT_PXO);
5243                 break;
5244         }
5245 }
5246
5247 // http banner stuff ---------------------------------------------
5248
5249 // init
5250 void multi_pxo_ban_init()
5251 {
5252         // zero the active banner bitmap
5253         Multi_pxo_banner.ban_bitmap = -1;       
5254
5255         // are we doing banners at all?
5256         if(os_config_read_uint(NULL, "PXOBanners", 1)){
5257                 // if we're already in idle mode, we're done downloading for this instance of freespace. pick a random image we already have
5258                 if(Multi_pxo_ban_mode == PXO_BAN_MODE_IDLE){
5259                         Multi_pxo_ban_mode = PXO_BAN_MODE_CHOOSE_RANDOM;                
5260                         return;
5261                 }
5262                 
5263                 // set ourselves to startup mode        
5264                 Multi_pxo_ban_mode = PXO_BAN_MODE_LIST_STARTUP;
5265                 Multi_pxo_ban_get = NULL;
5266         } else {
5267                 // set ourselves to idle mode
5268                 Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5269                 Multi_pxo_ban_get = NULL;
5270         }
5271
5272         // zero the active banner bitmap
5273         SDL_zero(Multi_pxo_banner);
5274         Multi_pxo_banner.ban_bitmap = -1;
5275 }
5276
5277 // process http download details
5278 void multi_pxo_ban_process()
5279 {
5280         char url_string[512] = "";
5281         char local_file[MAX_PATH_LEN] = "";
5282
5283         // process stuff
5284         switch(Multi_pxo_ban_mode){
5285         // start downloading list
5286         case PXO_BAN_MODE_LIST_STARTUP:         
5287                 // remote file
5288                 SDL_snprintf(url_string, SDL_arraysize(url_string), "%s/%s", Multi_options_g.pxo_banner_url, PXO_BANNERS_CONFIG_FILE);
5289
5290                 // local file
5291                 cf_create_default_path_string(local_file, CF_TYPE_MULTI_CACHE, PXO_BANNERS_CONFIG_FILE);
5292                 
5293                 // try creating the file get object
5294                 Multi_pxo_ban_get = new InetGetFile(url_string, local_file);
5295
5296                 // bad
5297                 if(Multi_pxo_ban_get == NULL){                  
5298                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5299                 }
5300
5301                 // go to the downloading list mode
5302                 Multi_pxo_ban_mode = PXO_BAN_MODE_LIST;
5303                 break;
5304
5305         // downloading list
5306         case PXO_BAN_MODE_LIST:
5307                 // error
5308                 if(Multi_pxo_ban_get->IsFileError()){                   
5309                         delete Multi_pxo_ban_get;
5310                         Multi_pxo_ban_get = NULL;
5311                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5312                         break;
5313                 } 
5314
5315                 // connecting, receiving
5316                 if(Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving()){
5317                         break;
5318                 }
5319
5320                 // done!
5321                 if(Multi_pxo_ban_get->IsFileReceived()){
5322                         delete Multi_pxo_ban_get;
5323                         Multi_pxo_ban_get = NULL;
5324                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_STARTUP;
5325                 }
5326                 break;
5327
5328         // start downloading files
5329         case PXO_BAN_MODE_IMAGES_STARTUP:
5330                 // first thing - parse the banners file and pick a file
5331                 multi_pxo_ban_parse_banner_file(0);
5332
5333                 // if we have no active file, we're done
5334                 if((strlen(Multi_pxo_banner.ban_file) <= 0) || (strlen(Multi_pxo_banner.ban_file_url) <= 0)){
5335                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5336                         break;
5337                 }
5338
5339                 // if the file already exists, we're done
5340                 if(cf_exist(Multi_pxo_banner.ban_file, CF_TYPE_MULTI_CACHE)){
5341                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5342                         break;
5343                 }
5344
5345                 // otherwise try and download it                                
5346                 cf_create_default_path_string(local_file, CF_TYPE_MULTI_CACHE, Multi_pxo_banner.ban_file);
5347                 // try creating the file get object
5348                 Multi_pxo_ban_get = new InetGetFile(Multi_pxo_banner.ban_file_url, local_file);
5349
5350                 // bad
5351                 if(Multi_pxo_ban_get == NULL){                  
5352                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5353                 }
5354
5355                 // go to the downloading images mode
5356                 Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES;
5357                 break;
5358
5359         // downloading files
5360         case PXO_BAN_MODE_IMAGES:
5361                 // error
5362                 if(Multi_pxo_ban_get->IsFileError()){                   
5363                         delete Multi_pxo_ban_get;
5364                         Multi_pxo_ban_get = NULL;
5365                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5366                         break;
5367                 } 
5368
5369                 // connecting, receiving
5370                 if(Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving()){
5371                         break;
5372                 }
5373
5374                 // done!
5375                 if(Multi_pxo_ban_get->IsFileReceived()){
5376                         delete Multi_pxo_ban_get;
5377                         Multi_pxo_ban_get = NULL;
5378                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5379                 }
5380                 break;
5381
5382         // done downloading - maybe load an image
5383         case PXO_BAN_MODE_IMAGES_DONE:
5384                 // make sure we have a valid filename
5385                 // SDL_assert(strlen(Multi_pxo_banner.ban_file) > 0);
5386                 if(strlen(Multi_pxo_banner.ban_file) > 0){
5387                         Multi_pxo_banner.ban_bitmap = bm_load(Multi_pxo_banner.ban_file);
5388                 }
5389
5390                 // now we're idle
5391                 Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5392                 break;
5393
5394         // idle (done with EVERYTHING)
5395         case PXO_BAN_MODE_IDLE:
5396                 // if the banner button was clicked
5397                 if(Multi_pxo_ban_button.pressed()){
5398                         multi_pxo_ban_clicked();                        
5399                 }
5400                 break;
5401
5402         case PXO_BAN_MODE_CHOOSE_RANDOM:
5403                 // first thing - parse the banners file and pick a file
5404                 multi_pxo_ban_parse_banner_file(1);
5405
5406                 Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5407                 break;
5408         }
5409 }
5410
5411 // close
5412 void multi_pxo_ban_close()
5413 {
5414         // if we have a currently active transfer
5415         if(Multi_pxo_ban_get != NULL){
5416                 Multi_pxo_ban_get->AbortGet();
5417                 delete Multi_pxo_ban_get;
5418                 Multi_pxo_ban_get = NULL;
5419         }
5420
5421         // if we have a loaded bitmap, unload it
5422         if(Multi_pxo_banner.ban_bitmap != -1){
5423                 bm_unload(Multi_pxo_banner.ban_bitmap);
5424                 Multi_pxo_banner.ban_bitmap = -1;
5425         }
5426 }
5427
5428 // parse the banners file and maybe fill in Multi_pxo_dl_file
5429 void multi_pxo_ban_parse_banner_file(int choose_existing)
5430 {
5431         char file_url[MAX_PATH_LEN] = "";
5432         char banners[10][MAX_PATH_LEN];
5433         char urls[10][MAX_PATH_LEN];
5434         int exists[10];
5435         int exist_count;
5436         int num_banners, idx;
5437         CFILE *in = cfopen(PXO_BANNERS_CONFIG_FILE, "rt", CFILE_NORMAL, CF_TYPE_MULTI_CACHE);
5438
5439         SDL_zero(Multi_pxo_banner);
5440         Multi_pxo_banner.ban_bitmap = -1;
5441
5442         // bad
5443         if(in == NULL){
5444                 return;
5445         }
5446
5447         // clear all strings
5448         SDL_zero(banners);
5449         SDL_zero(urls);
5450
5451         // get the global banner url
5452         if(cfgets(file_url, SDL_arraysize(file_url), in) == NULL){
5453                 cfclose(in);
5454                 cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE);
5455                 return;
5456         }
5457         drop_leading_white_space(file_url);
5458         drop_trailing_white_space(file_url);
5459
5460         // otherwise read in            
5461         num_banners = 0;
5462         while(num_banners < 10){
5463                 // try and get the pcx
5464                 if(cfgets(banners[num_banners], SDL_arraysize(banners[0]), in) == NULL){
5465                         break;
5466                 }
5467                 // try and get the url
5468                 if(cfgets(urls[num_banners], SDL_arraysize(urls[0]), in) == NULL){
5469                         break;
5470                 }
5471
5472                 // strip off trailing and leading whitespace
5473                 drop_leading_white_space(banners[num_banners]);
5474                 drop_trailing_white_space(banners[num_banners]);
5475                 drop_leading_white_space(urls[num_banners]);
5476                 drop_trailing_white_space(urls[num_banners]);
5477
5478                 // got one
5479                 num_banners++;          
5480         }
5481
5482         // close the file
5483         cfclose(in);
5484
5485         // no banners
5486         if(num_banners <= 0){           
5487                 return;
5488         }
5489
5490         // if we're only selecting files which already exist (previously downloaded)
5491         if(choose_existing){
5492                 // non exist
5493                 for(idx=0; idx<10; idx++){
5494                         exists[idx] = 0;
5495                 }
5496
5497                 // build a list of existing files
5498                 exist_count = 0;
5499                 for(idx=0; idx<num_banners; idx++){
5500                         if(cf_exist(banners[idx], CF_TYPE_MULTI_CACHE)){
5501                                 exists[idx] = 1;
5502                                 exist_count++;
5503                         }
5504                 }
5505
5506                 // bogus
5507                 if(exist_count <= 0){
5508                         return;
5509                 }
5510
5511                 // select one
5512                 int select = (int)frand_range(0.0f, (float)exist_count);
5513                 if(select >= exist_count){
5514                         select = exist_count - 1;
5515                 }
5516                 if(select < 0){
5517                         select = 0;
5518                 }
5519                 for(idx=0; idx<exist_count; idx++){
5520                         if(select == 0){
5521                                 break;
5522                         }
5523                         if(exists[idx]){
5524                                 select--;
5525                         }
5526                 }
5527
5528                 // valid?
5529                 if(idx < exist_count){
5530                         // base filename
5531                         SDL_strlcpy(Multi_pxo_banner.ban_file, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file));
5532
5533                         // get the full file url
5534                         SDL_strlcpy(Multi_pxo_banner.ban_file_url, file_url, SDL_arraysize(Multi_pxo_banner.ban_file_url));
5535                         SDL_strlcat(Multi_pxo_banner.ban_file_url, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file_url));
5536
5537                         // url of where to go to when clicked
5538                         SDL_strlcpy(Multi_pxo_banner.ban_url, urls[idx], SDL_arraysize(Multi_pxo_banner.ban_url));
5539                 }
5540         }
5541         // randomly pick a file for download
5542         else {                  
5543                 idx = (int)frand_range(0.0f, (float)num_banners);
5544                 
5545                 if(idx >= num_banners){
5546                         idx = num_banners - 1;
5547                 } 
5548                 if(idx < 0){
5549                         idx = 0;
5550                 }
5551
5552                 // base filename
5553                 SDL_strlcpy(Multi_pxo_banner.ban_file, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file));
5554
5555                 // get the full file url
5556                 SDL_strlcpy(Multi_pxo_banner.ban_file_url, file_url, SDL_arraysize(Multi_pxo_banner.ban_file_url));
5557                 SDL_strlcat(Multi_pxo_banner.ban_file_url, banners[idx], SDL_arraysize(Multi_pxo_banner.ban_file_url));
5558
5559                 // url of where to go to when clicked
5560                 SDL_strlcpy(Multi_pxo_banner.ban_url, urls[idx], SDL_arraysize(Multi_pxo_banner.ban_url));
5561         }
5562
5563         // delete the banner config file
5564         // cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE);
5565 }
5566
5567 // any bitmap or info or whatever
5568 void multi_pxo_ban_draw()
5569 {       
5570         // if we have a valid bitmap
5571         if(Multi_pxo_banner.ban_bitmap >= 0){
5572                 // if the mouse is over the banner button, highlight with a rectangle
5573                 if(Multi_pxo_ban_button.is_mouse_on()){
5574                         gr_set_color_fast(&Color_bright_blue);
5575                         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);
5576                 }
5577
5578                 // draw the bitmap itself
5579                 gr_set_bitmap(Multi_pxo_banner.ban_bitmap);
5580                 gr_bitmap(Pxo_ban_coords[gr_screen.res][0], Pxo_ban_coords[gr_screen.res][1]);
5581         }
5582 }
5583
5584 // called when the URL button is clicked
5585 void multi_pxo_ban_clicked()
5586 {
5587         // if we have a valid bitmap and URL, launch the URL
5588         if((Multi_pxo_banner.ban_bitmap >= 0) && (strlen(Multi_pxo_banner.ban_url) > 0)){
5589                 multi_pxo_url(Multi_pxo_banner.ban_url);
5590         }
5591 }
5592