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