]> icculus.org git repositories - taylor/freespace2.git/blob - src/network/multi_pxo.cpp
basic PXO cross-platform network compatibility
[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);
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);
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                 sprintf(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                 memset(Multi_pxo_channel_last, 0, MAX_CHANNEL_NAME_LEN + 1);
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);             
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                 strcat(anim_filename, MULTI_PXO_ANIM_FNAME);
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         memset(Multi_fs_tracker_channel,0,255);
1557         memset(Multi_fs_tracker_filter,0,255);
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                         strcpy(priv_chan.name,Multi_pxo_priv_chan);
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                                 strcpy(join.name,Multi_pxo_find_channel);
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         memset(Multi_fs_tracker_channel,0,255);
1652         memset(Multi_fs_tracker_filter,0,255);
1653         if( ON_CHANNEL() && strlen(Multi_pxo_channel_current.name) ){
1654                 // channel name
1655                 strcpy(Multi_fs_tracker_channel,Multi_pxo_channel_current.name);
1656                 
1657                 // filter name
1658                 strcpy(Multi_fs_tracker_filter,Multi_pxo_channel_current.name);
1659         } 
1660
1661         // disconnect from the server
1662         DisconnectFromChatServer();
1663         Multi_pxo_connected = 0;
1664
1665         // unload the animation 
1666         anim_release_all_instances(GS_STATE_PXO);
1667         Multi_pxo_anim_instance = NULL;
1668         if(Multi_pxo_anim != NULL){
1669                 anim_free(Multi_pxo_anim);
1670                 Multi_pxo_anim = NULL;
1671         }
1672
1673         // unload the palette for this screen
1674         multi_pxo_unload_palette();
1675         
1676         // destroy the UI_WINDOW
1677         Multi_pxo_window.destroy();
1678
1679         // clear the channel list
1680         multi_pxo_clear_channels();
1681
1682         // close the chat system
1683         multi_pxo_chat_free();
1684
1685         // close http stuff
1686         multi_pxo_ban_close();
1687 }
1688
1689 // run normally (no popups)
1690 void multi_pxo_do_normal()
1691 {               
1692         int validate_code;
1693         int k = Multi_pxo_window.process();
1694         
1695         // if the animation isn't playing, start it up
1696         if((Multi_pxo_anim_instance == NULL) && (Multi_pxo_anim != NULL)){
1697                 anim_play_struct aps;
1698
1699                 // fire up the animation
1700                 anim_play_init(&aps, Multi_pxo_anim, MULTI_PXO_ANIM_X, MULTI_PXO_ANIM_Y);
1701                 aps.screen_id = GS_STATE_PXO;
1702                 aps.framerate_independent = 1;                          
1703                 aps.looped = 1;
1704                 Multi_pxo_anim_instance = anim_play(&aps);                              
1705         }
1706
1707         // process any keypresses
1708         switch(k){
1709         case SDLK_ESCAPE:
1710                 gamesnd_play_iface(SND_USER_SELECT);
1711                 gameseq_post_event(GS_EVENT_MAIN_MENU);
1712                 break;  
1713         }               
1714
1715         // check for button presses
1716         multi_pxo_check_buttons();      
1717
1718         // if we're not in a chatroom, disable and hide the chat input box
1719         if(!ON_CHANNEL()){
1720                 Multi_pxo_chat_input.hide();
1721                 Multi_pxo_chat_input.disable();
1722         } else {
1723                 Multi_pxo_chat_input.enable();
1724                 Multi_pxo_chat_input.unhide();
1725         }       
1726
1727         // blit everything
1728         multi_pxo_blit_all();           
1729
1730         // flip the page
1731         gr_flip();
1732
1733         // verify version # now (only once per Freespace instance)      
1734         if(Multi_pxo_must_verify_version){
1735                 switch(multi_update_gobaby()){
1736                 // everything is cool. Move along
1737                 case MULTI_UPDATE_CONTINUE:
1738                         Multi_pxo_must_verify_version = 0;
1739                         break;
1740
1741                 // go back to the main menu
1742                 case MULTI_UPDATE_MAIN_MENU:
1743                         gameseq_post_event(GS_EVENT_MAIN_MENU);
1744                         
1745                         // unset these so we don't do anything else PXO related
1746                         Multi_pxo_must_validate = 0;
1747                         Multi_pxo_must_connect = 0;
1748                         break;
1749
1750                 // freespace will be shutting down shortly
1751                 case MULTI_UPDATE_SHUTTING_DOWN:
1752                         return;
1753                 }               
1754         }       
1755
1756         // if we need to get tracker info for ourselves, do so
1757         if(Multi_pxo_must_validate){
1758                 // initialize the master tracker API for Freespace
1759                 multi_fs_tracker_init();
1760
1761                 // validate the current player with the master tracker (will create the pilot on the MT if necessary)
1762                 validate_code = multi_fs_tracker_validate(0);
1763                 if(validate_code != 1){
1764                         // show an error popup if it failed (not cancelled by the user)
1765                         if (validate_code == 0) {
1766                                 switch (popup(PF_USE_AFFIRMATIVE_ICON | PF_WEB_CURSOR_1 | PF_WEB_CURSOR_2, 3, POPUP_CANCEL,XSTR("&Create Acct",936), XSTR("&Verify Acct",937), XSTR("PXO Login not accepted.  You may visit the Parallax Online website to create or verify your login.  Or you may click Cancel to play without using the Parallax Online service.  (You may switch back to Parallax Online from the Options Menu under the Multi tab.)",938))) {
1767                                         case 0:
1768                                                 nprintf(("Network","PXO CANCEL\n"));
1769
1770                                                 // flip his "pxo" bit temporarily and push him to the join game screen
1771                                                 Multi_options_g.pxo = 0;
1772                                                 // Net_game_tcp_mode = NET_TCP;
1773                                                 gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME);
1774                                                 break;
1775
1776                                         case 1:
1777                                                 nprintf(("Network","PXO CREATE\n"));
1778                                                 // fire up the given URL
1779                                                 multi_pxo_url(Multi_options_g.pxo_create_url);
1780                                                 break;
1781
1782                                         case 2:
1783                                                 nprintf(("Network","PXO VERIFY\n"));
1784                                                 // fire up the given URL
1785                                                 multi_pxo_url(Multi_options_g.pxo_verify_url);
1786                                                 break;
1787                                 }
1788                         }
1789
1790                         // go back to the main hall
1791                         gameseq_post_event(GS_EVENT_MAIN_MENU);
1792
1793                         Multi_pxo_must_connect = 0;
1794                         Multi_pxo_must_validate = 0;
1795                 }
1796                 // now we have to conenct to PXO
1797                 else {                  
1798                         Multi_pxo_must_connect = 1;
1799                         Multi_pxo_must_validate = 0;
1800                 }
1801         }
1802
1803         // if we need to connect, do so now
1804         if(Multi_pxo_must_connect){             
1805                 // for now, just try once
1806                 Multi_pxo_connected = multi_pxo_connect();
1807
1808                 // if we successfully connected, send a request for a list of channels on the server
1809                 if(Multi_pxo_connected){
1810                         multi_pxo_get_channels();
1811
1812                         // set our status
1813                         multi_pxo_set_status_text(XSTR("Retrieving Public Channels",939));
1814                 } else {
1815                         // set our status
1816                         multi_pxo_set_status_text(XSTR("Failed to connect to Parallax Online",940));
1817                 }
1818
1819                 // no longer need to connect
1820                 Multi_pxo_must_connect = 0;
1821         }
1822 }
1823
1824 // blit everything on the "normal" screen
1825 void multi_pxo_blit_all()
1826 {
1827         // draw the background, etc
1828         gr_reset_clip();        
1829         // GR_MAYBE_CLEAR_RES(Multi_pxo_bitmap);
1830         int bmap = Multi_pxo_bitmap;
1831         do  { 
1832                 int bmw = -1; 
1833                 int bmh = -1; 
1834                 if(bmap != -1){ 
1835                         bm_get_info( bmap, &bmw, &bmh); 
1836                         if((bmw != gr_screen.max_w) || (bmh != gr_screen.max_h)){
1837                                 gr_clear();
1838                         } 
1839                 } else {
1840                         gr_clear();
1841                 } 
1842         } while(0);
1843         if(Multi_pxo_bitmap != -1){
1844                 gr_set_bitmap(Multi_pxo_bitmap);
1845                 gr_bitmap(0,0);
1846         }
1847         Multi_pxo_window.draw();
1848
1849         // display the channel list
1850         multi_pxo_blit_channels();
1851
1852         // display the player list
1853         multi_pxo_blit_players();
1854
1855         // blit the chat text
1856         multi_pxo_chat_blit();
1857
1858         // blit the status text
1859         multi_pxo_blit_status_text();           
1860
1861         // blit and process the notification string
1862         multi_pxo_notify_blit();
1863
1864         // any bitmap or info or whatever
1865         multi_pxo_ban_draw();
1866
1867         // draw any motd stuff
1868         multi_pxo_motd_maybe_blit();
1869
1870         // if we have a valid animation handle, play it
1871         if(Multi_pxo_anim_instance != NULL){
1872                 anim_render_all(GS_STATE_PXO,flFrametime);
1873         }
1874 }
1875
1876 // process common stuff
1877 void multi_pxo_process_common()
1878 {
1879         // process the channel list (select, etc)
1880         multi_pxo_process_channels();
1881
1882         // process the player list (select, etc)
1883         multi_pxo_process_players();
1884
1885         // process chat controls
1886         multi_pxo_chat_process();
1887         
1888         // process http download details
1889         multi_pxo_ban_process();
1890 }
1891
1892 // get selected player information
1893 void multi_pxo_get_data(char *name)
1894 {
1895 }
1896
1897 // handle being kicked
1898 void multi_pxo_handle_kick()
1899 {
1900         // remove ourselves from the room       
1901         memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
1902         Multi_pxo_channel_current.num_users = -1;
1903
1904         // clear text
1905         multi_pxo_chat_clear();
1906
1907         // clear the old player list
1908         multi_pxo_clear_players();
1909
1910         // add a notification string
1911         multi_pxo_notify_add(XSTR("You have been kicked",941));
1912 }
1913
1914 // handle being disconnected
1915 void multi_pxo_handle_disconnect()
1916 {
1917         popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been disconnected from the server",942));
1918         gameseq_post_event(GS_EVENT_MAIN_MENU);
1919 }
1920
1921 // return string2, which is the first substring of string 1 without a space
1922 // it is safe to pass the same pointer for both parameters
1923 void multi_pxo_strip_space(char *string1,char *string2)
1924 {
1925         char midway[255];
1926         char *tok;
1927
1928         // copy the original
1929         strcpy(midway,string1);
1930         tok = strtok(midway," ");
1931         if(tok != NULL){
1932                 strcpy(string2,tok);
1933         } else {
1934                 strcpy(string2,"");
1935         }
1936 }
1937
1938 // fire up the given URL
1939 void multi_pxo_url(char *url)
1940 {
1941 #ifdef PLAT_UNIX
1942         STUB_FUNCTION;
1943 #else
1944         // execute the shell command
1945         int r = (int) ShellExecute(NULL, NOX("open"), url, NULL, NULL, SW_SHOW);
1946         if (r < 32) {           
1947                 switch (r) {
1948                         case 0: 
1949                         case ERROR_BAD_FORMAT: 
1950                         case SE_ERR_ACCESSDENIED: 
1951                         case SE_ERR_ASSOCINCOMPLETE: 
1952                         case SE_ERR_DDEBUSY:
1953                         case SE_ERR_DDEFAIL:
1954                         case SE_ERR_DDETIMEOUT:
1955                         case SE_ERR_DLLNOTFOUND:
1956                         case SE_ERR_OOM:
1957                         case SE_ERR_SHARE:                      
1958                         case SE_ERR_NOASSOC:
1959                         case ERROR_FILE_NOT_FOUND:
1960                         case ERROR_PATH_NOT_FOUND:
1961                                 popup(PF_USE_AFFIRMATIVE_ICON | PF_TITLE_RED | PF_TITLE_BIG,1,POPUP_OK,XSTR("Warning\nCould not locate/launch default Internet Browser",943));
1962                                 break;
1963                 }                                       
1964         }
1965 #endif
1966 }
1967
1968 // load/set the palette
1969 void multi_pxo_load_palette()
1970 {
1971         // use the palette
1972 #ifndef HARDWARE_ONLY
1973         palette_use_bm_palette(Multi_pxo_palette);
1974 #endif
1975 }
1976
1977 // unload the palette
1978 void multi_pxo_unload_palette()
1979 {
1980         // unload the palette if it exists
1981         if(Multi_pxo_palette != -1){
1982                 bm_release(Multi_pxo_palette);
1983                 Multi_pxo_palette = -1;
1984         }
1985 }
1986
1987 // if we're currently on a private channel
1988 int multi_pxo_on_private_channel()
1989 {
1990         // if we're connected to a channel with the "+" symbol on front
1991         if(ON_CHANNEL() && (Multi_pxo_channel_current.name[0] == '+')){
1992                 return 1;
1993         }
1994
1995         // otherwise return falos
1996         return 0;
1997 }
1998
1999 // convert string 1 into string 2, substituting underscores for spaces
2000 void multi_pxo_underscore_nick(char *string1,char *string2)
2001 {
2002         char nick_temp[512];
2003         char *tok;
2004         
2005         // don't do anything if we have bogus string
2006         if((string1 == NULL) || (string2 == NULL)){
2007                 return;
2008         }
2009
2010         // copy the nickname
2011         memset(nick_temp,0,512);
2012         strcpy(nick_temp,string1);
2013
2014         // get the first token
2015         tok = strtok(nick_temp," ");
2016         if(tok != NULL){
2017                 strcpy(string2,tok);
2018
2019                 // get the next token
2020                 tok = strtok(NULL," ");
2021                 while(tok != NULL){                             
2022                         if(tok != NULL){
2023                                 strcat(string2,"_");
2024                                 strcat(string2,tok);
2025                         }
2026
2027                         tok = strtok(NULL," ");
2028                 }
2029         } else {
2030                 strcpy(string2,string1);
2031         }
2032 }
2033
2034 // if the command is a potential "nick" command
2035 int multi_pxo_is_nick_command(char *msg)
2036 {
2037         char *tok;
2038         char tmp[512];
2039
2040         // get the first token in the message
2041         memset(tmp,0,512);
2042         strcpy(tmp,msg);
2043         tok = strtok(tmp," ");
2044         if(tok == NULL){
2045                 // can't be a nick message
2046                 return 0;
2047         }
2048
2049         return !SDL_strcasecmp(tok,NOX("/nick"));
2050 }
2051
2052 // check for button presses
2053 void multi_pxo_check_buttons()
2054 {
2055         int idx;
2056
2057         // go through all buttons
2058         for(idx=0;idx<MULTI_PXO_NUM_BUTTONS;idx++){
2059                 if(Multi_pxo_buttons[gr_screen.res][idx].button.pressed()){
2060                         multi_pxo_button_pressed(idx);
2061                         break;
2062                 }
2063         }
2064 }
2065
2066 // handle a button press
2067 void multi_pxo_button_pressed(int n)
2068 {
2069         switch(n){
2070         case MULTI_PXO_EXIT:
2071                 gamesnd_play_iface(SND_USER_SELECT);
2072                 gameseq_post_event(GS_EVENT_MAIN_MENU);
2073                 break;
2074
2075         case MULTI_PXO_CHAN_UP:
2076                 multi_pxo_scroll_channels_up();
2077                 break;
2078
2079         case MULTI_PXO_CHAN_DOWN:
2080                 multi_pxo_scroll_channels_down();
2081                 break;
2082
2083         case MULTI_PXO_TEXT_UP:
2084                 multi_pxo_scroll_chat_up();
2085                 break;
2086
2087         case MULTI_PXO_TEXT_DOWN:
2088                 multi_pxo_scroll_chat_down();
2089                 break;
2090
2091         case MULTI_PXO_PLIST_UP:
2092                 multi_pxo_scroll_players_up();
2093                 multi_pxo_chat_adjust_start();
2094                 break;
2095
2096         case MULTI_PXO_PLIST_DOWN:
2097                 multi_pxo_scroll_players_down();                
2098                 multi_pxo_chat_adjust_start();          
2099                 break;
2100
2101         case MULTI_PXO_JOIN:
2102                 // if there are no channels to join, let the user know
2103                 if((Multi_pxo_channel_count == 0) || (Multi_pxo_channels == NULL)){
2104                         gamesnd_play_iface(SND_GENERAL_FAIL);
2105                         multi_pxo_notify_add(XSTR("No channels!",944));
2106                         break;
2107                 }
2108
2109                 // if we're not already trying to join, allow this
2110                 if(!SWITCHING_CHANNELS() && (Multi_pxo_channel_select != NULL)){
2111                         gamesnd_play_iface(SND_USER_SELECT);
2112                         multi_pxo_join_channel(Multi_pxo_channel_select);
2113                 } else {
2114                         multi_pxo_notify_add(XSTR("Already trying to join a channel!",945));
2115                         gamesnd_play_iface(SND_GENERAL_FAIL);
2116                 }
2117                 break;
2118
2119         case MULTI_PXO_GAMES:
2120                 // move to the join game screen as normally (temporary!)
2121                 gameseq_post_event( GS_EVENT_MULTI_JOIN_GAME );
2122                 break;
2123
2124         case MULTI_PXO_JOIN_PRIV:
2125                 // if we're not already trying to join, allow this
2126                 if(!SWITCHING_CHANNELS()){
2127                         gamesnd_play_iface(SND_USER_SELECT);
2128
2129                         // fire up the private join popup
2130                         multi_pxo_priv_popup();
2131                 } else {
2132                         multi_pxo_notify_add(XSTR("Already trying to join a channel!",945));
2133                         gamesnd_play_iface(SND_GENERAL_FAIL);
2134                 }               
2135                 break;
2136
2137         case MULTI_PXO_FIND:
2138                 gamesnd_play_iface(SND_USER_SELECT);
2139
2140                 // fire up the find join popup
2141                 multi_pxo_find_popup();
2142                 break;
2143
2144         case MULTI_PXO_HELP:
2145                 gamesnd_play_iface(SND_USER_SELECT);
2146                 gameseq_post_event(GS_EVENT_PXO_HELP);
2147                 break;
2148
2149         case MULTI_PXO_PINFO:
2150                 char stats[255];
2151
2152                 // if we have a guy selected, try and get his info
2153                 if(Multi_pxo_player_select != NULL){
2154                         // if we successfully got info for this guy
2155                         if(multi_pxo_pinfo_get(Multi_pxo_player_select->name)){                         
2156                                 // show the stats
2157                                 multi_pxo_pinfo_show();                         
2158                         }
2159                         // if we didn't get stats for this guy.
2160                         else {
2161                                 memset(stats,0,255);
2162                                 sprintf(stats,XSTR("Could not get stats for %s\n(May not be a registered pilot)",946),Multi_pxo_player_select->name);
2163                                 popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,stats);
2164                         }
2165                 } else {
2166                         gamesnd_play_iface(SND_GENERAL_FAIL);
2167                 }
2168                 break;
2169
2170         case MULTI_PXO_RANKINGS:                
2171                 // make sure he doesn't click it too many times
2172                 if((Multi_pxo_ranking_last < 0.0f) || ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_ranking_last) > MULTI_PXO_RANK_TIME) ){
2173                         gamesnd_play_iface(SND_USER_SELECT);
2174                         
2175                         // fire up the url
2176                         multi_pxo_url(Multi_options_g.pxo_rank_url);
2177
2178                         // mark the time down
2179                         Multi_pxo_ranking_last = f2fl(timer_get_fixed_seconds());
2180                 } else {
2181                         gamesnd_play_iface(SND_GENERAL_FAIL);
2182                 }
2183                 break;
2184
2185         case MULTI_PXO_MOTD:
2186                 // maybe fire up the pxo motd dialog
2187                 multi_pxo_motd_dialog();
2188                 break;
2189         }
2190 }
2191
2192 // condition function for popup_do_with_condition for connected to Parallax Online
2193 int mpxo_failed = 0;
2194 int multi_pxo_connect_do()
2195 {
2196         int ret_code;           
2197         char id_string[255] = "";
2198         char ip_string[255] = "";       
2199
2200         // if we already tried and failed, sit around until the user presses cancel
2201         if(!mpxo_failed){       
2202                 // try and connect to the server        
2203                 SDL_assert(Player);
2204
2205                 // build the tracker id string
2206                 memset(id_string, 0, 255);
2207                 sprintf(id_string, "%s %s", Multi_tracker_id_string, Player->callsign);
2208                 
2209                 // build the ip string
2210                 memset(ip_string, 0, 255);
2211                 sprintf(ip_string, "%s:%d", Multi_options_g.pxo_ip, PXO_CHAT_PORT);
2212
2213                 // connect to the server
2214                 ret_code = ConnectToChatServer(ip_string, Multi_pxo_nick, id_string);           
2215                 
2216                 // give some time to the pxo api.
2217                 multi_pxo_api_process();        
2218
2219                 switch(ret_code){
2220                 // already connected, return success
2221                 case -2:
2222                         return 10;
2223
2224                 // failed to connect, return fail
2225                 case -1 :
2226                         mpxo_failed = 1;
2227                         popup_change_text(XSTR("Failed to connect to Parallax Online!", 947));
2228                         return 0;
2229
2230                 // connected, return success
2231                 case 1 :
2232                         return 10;
2233
2234                 // still connecting
2235                 case 0 :                        
2236                         return 0;
2237                 }
2238         }
2239
2240         return 0;
2241 }
2242
2243 // popup loop which does an autojoin of a public channel.  Returns when the autojoin process is complete
2244 int multi_pxo_autojoin_do()
2245 {
2246         pxo_channel last_channel;
2247
2248         // if we need to autojoin, do so now
2249         if(Multi_pxo_must_autojoin){
2250                 Multi_pxo_must_autojoin = 0;
2251
2252                 // if we're supposed to be using a (valid) "last" channel, do so
2253                 if(Multi_pxo_use_last_channel && strlen(Multi_pxo_channel_last)){
2254                         // setup the data
2255                         memset(&last_channel, 0, sizeof(pxo_channel));
2256                         last_channel.num_users = 0;
2257                         strcpy(last_channel.name, Multi_pxo_channel_last);
2258
2259                         // join the channel
2260                         multi_pxo_join_channel(&last_channel);
2261
2262                         nprintf(("Network","PXO : using last channel\n"));
2263                 } else {
2264                         multi_pxo_autojoin();
2265
2266                         nprintf(("Network","PXO : using autojoin channel\n"));
2267                 }
2268                 multi_pxo_get_channels();
2269         }
2270
2271         // give some time to the pxo api.
2272         multi_pxo_api_process();        
2273         multi_pxo_process_common();
2274
2275         // next value is not -1 when actually switching channels, so keep processing by returning 0.
2276         if ( SWITCHING_CHANNELS() ){
2277                 return 0;
2278         }
2279
2280         // couldn't switch channel for some reason.  bail out with -1
2281         if ( !ON_CHANNEL() ){
2282                 return -1;
2283         }
2284
2285         // return success
2286         return 1;
2287 }
2288
2289 // attempt to connect to Parallax Online, return success or fail
2290 int multi_pxo_connect()
2291 {
2292         char join_str[256];     
2293         char join_fail_str[256];
2294         
2295         // intiialize chat api
2296         ChatInit();
2297
2298         // set us to "must autojoin"
2299         Multi_pxo_must_autojoin = 1;
2300
2301         // run the connect dialog/popup
2302         mpxo_failed = 0;
2303         if(popup_till_condition(multi_pxo_connect_do, XSTR("&Cancel", 779), XSTR("Logging into Parallax Online",949)) == 10){
2304                 int rval;
2305
2306                 memset(join_str,0,256);
2307                 memset(join_fail_str,0,256);
2308                 // if we're going to use the "last" channel
2309                 if(Multi_pxo_use_last_channel && strlen(Multi_pxo_channel_last)){                       
2310                         strcpy(join_str, XSTR("Joining last channel (",982));
2311                         strcat(join_str, Multi_pxo_channel_last + 1);
2312                         strcat(join_str, ")");
2313
2314                         strcpy(join_fail_str, XSTR("Unable to join last channel", 983));
2315                 } else {
2316                         strcpy(join_str, XSTR("Autojoining public channel", 984));
2317                         strcpy(join_fail_str, XSTR("Unable to autojoin public channel", 985));
2318                 }
2319
2320                 // once connected, we should do an autojoin before allowing the guy to continue.
2321                 rval = popup_till_condition( multi_pxo_autojoin_do, XSTR("&Cancel", 779), join_str );
2322                 if ( rval == 1 ) {
2323                         return 1;
2324                 }
2325
2326                 popup( PF_USE_AFFIRMATIVE_ICON, 1, XSTR("OK", 1492), join_fail_str);
2327         }
2328
2329         // otherwise disconnect just to be safe
2330         DisconnectFromChatServer();
2331         gameseq_post_event(GS_EVENT_MAIN_MENU);
2332
2333         // did not successfully connect
2334         return 0;
2335 }
2336
2337 // run the networking functions for the PXO API
2338 void multi_pxo_api_process()
2339 {
2340         char *p;
2341         char msg_str[512];
2342         Chat_command *cmd;      
2343         pxo_channel *lookup;
2344
2345         // give some time to psnet
2346         PSNET_TOP_LAYER_PROCESS();
2347
2348         // give some time to the game tracker API
2349         IdleGameTracker();
2350
2351         // give some time to the user tracker API
2352         PollPTrackNet();
2353         
2354         // get any incoming text 
2355         do
2356         {
2357                 p = GetChatText();
2358                 if(p)
2359                 {                                               
2360                         // process the chat line
2361                         multi_pxo_chat_process_incoming(p);
2362                 }
2363         } while(p);
2364         
2365         // get any incoming channel list stuff
2366         p = GetChannelList();
2367         if(p)
2368         {
2369                 // nprintf(("Network","%s\n",p));
2370                 multi_pxo_make_channels(p);
2371         }       
2372         
2373         // process any chat commands
2374         cmd = GetChatCommand();
2375         while(cmd)
2376         {               
2377                 switch(cmd->command)
2378                 {                       
2379                 case CC_USER_JOINING:                   
2380                         // add a user, if he doesn't already exist
2381                         if(multi_pxo_find_player(cmd->data) == NULL){
2382                                 multi_pxo_add_player(cmd->data);
2383                         }
2384
2385                         // increase the player count
2386                         if(ON_CHANNEL()){
2387                                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2388                                 if(lookup != NULL){
2389                                         lookup->num_users++;
2390                                 }
2391                         }
2392                         break;
2393                 
2394                 case CC_USER_LEAVING:                   
2395                         // delete a user
2396                         multi_pxo_del_player(cmd->data);
2397
2398                         // add a text message
2399                         memset(msg_str,0,512);
2400                         sprintf(msg_str, XSTR("*** %s has left", 950), cmd->data);                      
2401                         multi_pxo_chat_process_incoming(msg_str);
2402
2403                         // decrease the player count
2404                         if(ON_CHANNEL()){
2405                                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2406                                 if(lookup != NULL){
2407                                         lookup->num_users--;
2408                                 }
2409                         }
2410                         break;
2411                 
2412                 case CC_DISCONNECTED:
2413                         multi_pxo_handle_disconnect();
2414                         break;
2415                 
2416                 case CC_KICKED:
2417                         multi_pxo_handle_kick();
2418                         break;
2419
2420                 case CC_NICKCHANGED:
2421                         // process a nick change
2422                         multi_pxo_process_nick_change(cmd->data);                       
2423                         break;
2424
2425                 case CC_YOURCHANNEL:
2426                         // copy the current channel info, and unset the switching status
2427                         memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
2428                         Multi_pxo_channel_switch.num_users = -1;                        
2429
2430                         SetNewChatChannel(NULL);
2431
2432                         strcpy(Multi_pxo_channel_current.name,cmd->data);
2433
2434                         // if we don't already have this guy on the list, add him
2435                         pxo_channel *lookup;
2436                         lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2437                         if(lookup == NULL){
2438                                 // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2439                                 lookup = multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
2440                         }
2441
2442                         // set the user count to be 0
2443                         if(lookup != NULL){
2444                                 lookup->num_users = 0;
2445                         }
2446
2447                         // set our "last" channel to be this one
2448                         strcpy(Multi_pxo_channel_last, Multi_pxo_channel_current.name);
2449
2450                         // refresh current channel server count
2451                         multi_pxo_channel_refresh_current();
2452
2453                         // clear the chat area
2454                         // multi_pxo_chat_clear();              
2455                         break;
2456                 
2457                 default:
2458                         Int3();
2459                 }
2460
2461                 cmd = GetChatCommand();
2462         }       
2463
2464         // handle any processing details if we're currently trying to join a channel
2465         multi_pxo_handle_channel_change();
2466 }
2467
2468 // process a "nick" change event
2469 void multi_pxo_process_nick_change(char *data)
2470 {
2471         char *from, *to;
2472         player_list *lookup;    
2473         
2474         // get the new string
2475         from = strtok(data," ");
2476         to = strtok(NULL,"");
2477         if((from != NULL) && (to != NULL)){
2478                 lookup = multi_pxo_find_player(from);
2479                 if(lookup != NULL){
2480                         strcpy(lookup->name,to);
2481
2482                         // if this is also my nick, change it
2483                         if(!SDL_strcasecmp(Multi_pxo_nick,from)){
2484                                 strcpy(Multi_pxo_nick,to);
2485                         }
2486                 }               
2487         }       
2488 }
2489
2490 // autojoin an appropriate channel
2491 void multi_pxo_autojoin()
2492 {
2493         pxo_channel sw;
2494
2495         memset(&sw,0,sizeof(pxo_channel));
2496         sw.num_users = 0;
2497         strcpy(sw.name,MULTI_PXO_AUTOJOIN_CHANNEL);
2498
2499         // if we found a valid room, attempt to join it 
2500         multi_pxo_join_channel(&sw);            
2501 }
2502
2503 // does the string match the "autojoin" prefic
2504 int multi_pxo_is_autojoin(char *name)
2505 {
2506         // check to see if the name is long enough
2507         if(strlen(name) < strlen(MULTI_PXO_AUTOJOIN_PREFIX)){
2508                 return 0;
2509         }
2510
2511         // check to see if the first n chars match
2512         return !SDL_strncasecmp(name,MULTI_PXO_AUTOJOIN_PREFIX,strlen(MULTI_PXO_AUTOJOIN_PREFIX));
2513 }
2514
2515 // called from the game tracker API - server count update for a channel
2516 void multi_pxo_channel_count_update(char *name,int count)
2517 {
2518         pxo_channel *lookup;
2519         
2520         // lookup the channel name on the normal list   
2521         lookup = NULL;
2522         lookup = multi_pxo_find_channel(name,Multi_pxo_channels);
2523         if(lookup != NULL){
2524                 lookup->num_servers = (ushort)count;
2525
2526                 nprintf(("Network","PXO : updated channel %s server count to %d\n",name,count));
2527         }       
2528 }
2529
2530 // status bar stuff -----------------------------------------------
2531
2532 // set the status text
2533 void multi_pxo_set_status_text(const char *txt)
2534 {
2535         // copy in the text
2536         memset(Multi_pxo_status_text, 0, 255);
2537         strncpy(Multi_pxo_status_text, txt, 254);
2538
2539         // make sure it fits properly
2540         gr_force_fit_string(Multi_pxo_status_text, 254, Multi_pxo_status_coords[gr_screen.res][2]);
2541 }
2542
2543 // blit the status text
2544 void multi_pxo_blit_status_text()
2545 {
2546         int w;
2547
2548         // center and draw the text
2549         if(strlen(Multi_pxo_status_text)) {
2550                 gr_set_color_fast(&Color_bright);
2551                 gr_get_string_size(&w, NULL, Multi_pxo_status_text);
2552                 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);
2553         }
2554 }
2555
2556
2557 // channel related stuff -------------------------------------------
2558
2559 // get a list of channels on the server
2560 void multi_pxo_get_channels()
2561 {               
2562         SendChatString(NOX("/list"));
2563 }
2564
2565 // clear the old channel list
2566 void multi_pxo_clear_channels()
2567 {
2568         pxo_channel *moveup,*backup;
2569         
2570         // only clear a non-null list
2571         if(Multi_pxo_channels != NULL){         
2572                 // otherwise
2573                 moveup = Multi_pxo_channels;
2574                 backup = NULL;
2575                 if(moveup != NULL){
2576                         do {                    
2577                                 backup = moveup;
2578                                 moveup = moveup->next;                  
2579                 
2580                                 // free the struct itself
2581                                 free(backup);
2582                                 backup = NULL;
2583                         } while(moveup != Multi_pxo_channels);
2584                         Multi_pxo_channels = NULL;
2585                 }       
2586
2587                 // head of the list of available channels
2588                 Multi_pxo_channels = NULL;
2589                 Multi_pxo_channel_count = 0;
2590
2591                 // item we're going to start displaying at
2592                 Multi_pxo_channel_start = NULL;
2593                 Multi_pxo_channel_start_index = -1;
2594
2595                 // items we've currently got selected
2596                 Multi_pxo_channel_select = NULL;
2597         }       
2598 }
2599
2600 // parse the input string and make a list of new channels
2601 void multi_pxo_make_channels(char *chan_str)
2602 {       
2603         char *name_tok,*user_tok,*desc_tok;
2604         pxo_channel *res;
2605         pxo_channel *lookup;
2606         int num_users;
2607         
2608         nprintf(("Network","Making some channels!\n"));
2609
2610         // clear the channel list
2611         // multi_pxo_clear_channels();  
2612
2613         // set the last get time
2614         Multi_pxo_channel_last_refresh = f2fl(timer_get_fixed_seconds());
2615
2616         name_tok = strtok(chan_str," ");
2617         if(name_tok == NULL){
2618                 return;
2619         } 
2620         name_tok += 1;
2621         do {
2622                 // parse the user count token           
2623                 user_tok = strtok(NULL," ");
2624
2625                 // parse the channel description token
2626                 desc_tok = strtok(NULL,"$");
2627
2628                 // something invalid in the data, return here.....
2629                 if((name_tok == NULL) || (user_tok == NULL) || (desc_tok == NULL)){
2630                         return;
2631                 }
2632
2633                 // get the # of users
2634                 num_users = 0;          
2635                 num_users = (ubyte)atoi(user_tok);              
2636
2637                 // if the # of users is > 0, or its not an autojoin, place it on the display list
2638                 if((num_users > 0) || !multi_pxo_is_autojoin(name_tok)){
2639                         // see if it exists already, and if so, just update the user count
2640                         lookup = multi_pxo_find_channel(name_tok,Multi_pxo_channels);
2641                         
2642                         if(lookup != NULL){
2643                                 lookup->num_users = (short)num_users;
2644                         }
2645                         // add the channel
2646                         else {
2647                                 res = multi_pxo_add_channel(name_tok,&Multi_pxo_channels);
2648                                 if(res != NULL){
2649                                         //Multi_pxo_channel_count++;
2650                                         res->num_users = (short)num_users;
2651                                         strcpy(res->desc,desc_tok);
2652                                 }               
2653                         }
2654                 }                               
2655
2656                 // get the next name token
2657                 name_tok = strtok(NULL," ");
2658         } while(name_tok != NULL);
2659                 
2660         // if we need to autojoin, do so now
2661         //if(Multi_pxo_must_autojoin){
2662         //      Multi_pxo_must_autojoin = 0;
2663         //      
2664         //      multi_pxo_autojoin();
2665         //}
2666
2667         // refresh channels
2668         multi_pxo_set_status_text(XSTR("Connected to Parallax Online",951));    
2669
2670         // if we haven't refreshed server counts yet, do it now
2671         if(Multi_pxo_channel_server_refresh < 0.0f){
2672                 multi_pxo_channel_refresh_servers();
2673         }
2674
2675         // if we don't already have this guy on the list, add him
2676         if(ON_CHANNEL()){
2677                 pxo_channel *lookup;
2678                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2679                 if(lookup == NULL){
2680                         // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2681                         multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
2682                 }
2683         }
2684 }
2685
2686 // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
2687 pxo_channel *multi_pxo_add_channel(char *name,pxo_channel **list)
2688 {
2689         pxo_channel *new_channel;
2690
2691         // try and allocate a new pxo_channel struct
2692         new_channel = (pxo_channel *)malloc(sizeof(pxo_channel));
2693         if ( new_channel == NULL ) {
2694                 nprintf(("Network", "Cannot allocate space for new pxo_channel structure\n"));
2695                 return NULL;
2696         }       
2697         memset(new_channel,0,sizeof(pxo_channel));
2698         // try and allocate a string for the channel name
2699         strncpy(new_channel->name,name,MAX_CHANNEL_NAME_LEN);   
2700
2701         // insert it on the list
2702         if ( *list != NULL ) {
2703                 new_channel->next = (*list)->next;
2704                 new_channel->next->prev = new_channel;
2705                 (*list)->next = new_channel;
2706                 new_channel->prev = *list;
2707         } else {
2708                 *list = new_channel;
2709                 (*list)->next = (*list)->prev = *list;
2710         }
2711                 
2712         Multi_pxo_channel_count++;
2713         return new_channel;
2714 }
2715
2716 // lookup a channel with the specified name
2717 pxo_channel *multi_pxo_find_channel(char *name,pxo_channel *list)
2718 {
2719         pxo_channel *moveup;
2720
2721         // look the sucker up
2722         moveup = list;
2723         if(moveup == NULL){
2724                 return NULL;
2725         } 
2726         do {
2727                 if(!SDL_strcasecmp(name,moveup->name)){
2728                         return moveup;
2729                 }
2730
2731                 moveup = moveup->next;
2732         } while((moveup != list) && (moveup != NULL));
2733
2734         return NULL;
2735 }
2736
2737 // process the channel list (select, etc)
2738 void multi_pxo_process_channels()
2739 {
2740         int item_index,my;
2741         int idx;
2742         
2743         // if we don't have a start item, but the list is non-null
2744         if((Multi_pxo_channel_start == NULL) && (Multi_pxo_channels != NULL)){
2745                 Multi_pxo_channel_start = Multi_pxo_channels;
2746                 Multi_pxo_channel_start_index = 0;
2747         } 
2748
2749         // if we don't have a selected item, but the list is non-null
2750         if((Multi_pxo_channel_select == NULL) && (Multi_pxo_channels != NULL)){
2751                 Multi_pxo_channel_select = Multi_pxo_channels;
2752
2753                 // set the text
2754                 multi_pxo_set_status_text(Multi_pxo_channel_select->desc);
2755         }
2756
2757         // if the "switch" delay timestamp is set, see if it has expired
2758         if((Multi_pxo_switch_delay != -1) && timestamp_elapsed(Multi_pxo_switch_delay)){
2759                 Multi_pxo_switch_delay = -1;
2760         }
2761
2762         // see if we have a mouse click on the channel region
2763         if(Multi_pxo_channel_button.pressed()){
2764                 Multi_pxo_channel_button.get_mouse_pos(NULL,&my);
2765
2766                 // index from the top
2767                 item_index = my / 10;
2768
2769                 // select the item if possible
2770                 if((item_index + Multi_pxo_channel_start_index) < Multi_pxo_channel_count){
2771                         Multi_pxo_channel_select = Multi_pxo_channel_start;
2772                         for(idx=0;idx<item_index;idx++){
2773                                 Multi_pxo_channel_select = Multi_pxo_channel_select->next;
2774                         }
2775
2776                         // set the text
2777                         multi_pxo_set_status_text(Multi_pxo_channel_select->desc);
2778                 }
2779         }
2780
2781         // last refresh time
2782         if((Multi_pxo_channel_last_refresh > 0.0f) && ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_channel_last_refresh) > CHANNEL_REFRESH_TIME) ){
2783                 // refresh channels
2784                 multi_pxo_set_status_text(XSTR("Refreshing Public Channel List",952));                          
2785
2786                 // get a list of channels on the server (clear any old list as well)
2787                 multi_pxo_get_channels();
2788
2789                 // refresh
2790                 Multi_pxo_channel_last_refresh = -1.0f;
2791
2792                 nprintf(("Network","Refreshing channels\n"));
2793         }
2794
2795         // if we haven't updated our server channel counts in a while, do so again
2796         // last refresh time
2797         if((Multi_pxo_channel_server_refresh > 0.0f) && ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_channel_server_refresh) > CHANNEL_SERVER_REFRESH_TIME) ){
2798                 // refresh server counts
2799                 // multi_pxo_set_status_text("Refreshing Public Channel Server Counts");
2800
2801                 // do it _NOW_ I"M RIGHT HERE KILL ME WHAT ARE YOU WAITING FOR DO IT KILL ME DO IT NOW!
2802                 multi_pxo_channel_refresh_servers();            
2803         }       
2804 }
2805
2806 // send a request to refresh our channel server counts
2807 void multi_pxo_channel_refresh_servers()
2808 {
2809         pxo_channel *lookup;
2810         filter_game_list_struct filter;
2811         
2812         // traverse the list of existing channels we know about and query the game tracker about them
2813         lookup = Multi_pxo_channels;
2814         if(lookup == NULL){
2815                 return;
2816         }
2817         do {
2818                 if(strlen(lookup->name)){
2819                         // copy in the info
2820                         memset(&filter,0,sizeof(filter_game_list_struct));
2821                         strcpy(filter.channel,lookup->name);
2822                         
2823                         // send the request
2824                         RequestGameCountWithFilter(&filter);
2825                 }
2826
2827                 // next item
2828                 lookup = lookup->next;
2829         } while((lookup != NULL) && (lookup != Multi_pxo_channels));
2830
2831         // record the time
2832         Multi_pxo_channel_server_refresh = f2fl(timer_get_fixed_seconds());
2833 }
2834
2835 // refresh current channel server count
2836 void multi_pxo_channel_refresh_current()
2837 {
2838         // send a request for a server count on this channel
2839         if(strlen(Multi_pxo_channel_current.name)){
2840                 // fill in the data
2841                 filter_game_list_struct filter;
2842                 memset(&filter,0,sizeof(filter_game_list_struct));
2843                 strcpy(filter.channel,Multi_pxo_channel_current.name);
2844
2845                 // send the request
2846                 RequestGameCountWithFilter(&filter);
2847         }               
2848 }
2849
2850 // display the channel list
2851 void multi_pxo_blit_channels()
2852 {
2853         pxo_channel *moveup;
2854         char chan_name[255];
2855         char chan_users[15];
2856         char chan_servers[15];
2857         int user_w,server_w;
2858         int disp_count,y_start;
2859
2860         // blit as many channels as we can
2861         disp_count = 0;
2862         y_start = Multi_pxo_chan_coords[gr_screen.res][1];
2863         moveup = Multi_pxo_channel_start;
2864         if(moveup == NULL){
2865                 return;
2866         }
2867         do {            
2868                 // if this is the currently selected item, highlight it
2869                 if(moveup == Multi_pxo_channel_select){
2870                         gr_set_color_fast(&Color_bright);
2871                 }
2872                 // otherwise draw it normally
2873                 else {
2874                         gr_set_color_fast(&Color_normal);
2875                 }
2876
2877                 // get the # of users on the channel
2878                 memset(chan_users, 0, 15);
2879                 sprintf(chan_users, "%d", moveup->num_users);
2880
2881                 // get the width of the user count string
2882                 gr_get_string_size(&user_w, NULL, chan_users);
2883
2884                 // get the # of servers on the channel
2885                 memset(chan_servers,0,15);
2886                 sprintf(chan_servers, "%d", moveup->num_servers);
2887
2888                 // get the width of the user count string
2889                 gr_get_string_size(&server_w, NULL, chan_servers);
2890
2891                 // make sure the name fits
2892                 memset(chan_name, 0, 10);
2893                 SDL_assert(moveup->name);
2894                 strcpy(chan_name,moveup->name);
2895                 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]);
2896
2897                 // blit the strings
2898                 gr_string(Multi_pxo_chan_coords[gr_screen.res][0], y_start, chan_name + 1);
2899                 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);
2900                 gr_set_color_fast(&Color_bright);
2901                 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);
2902
2903                 // increment the displayed count
2904                 disp_count++;
2905                 y_start += 10;          
2906
2907                 // next item
2908                 moveup = moveup->next;
2909         } while((moveup != Multi_pxo_channels) && (disp_count < Multi_pxo_max_chan_display[gr_screen.res]));
2910 }
2911
2912 // scroll channel list up
2913 void multi_pxo_scroll_channels_up()
2914 {               
2915         // if we're already at the head of the list, do nothing
2916         if((Multi_pxo_channel_start == NULL) || (Multi_pxo_channel_start == Multi_pxo_channels)){
2917                 gamesnd_play_iface(SND_GENERAL_FAIL);           
2918                 return;
2919         }
2920         
2921         // otherwise move up one
2922         Multi_pxo_channel_start = Multi_pxo_channel_start->prev;
2923         Multi_pxo_channel_start_index--;
2924         gamesnd_play_iface(SND_USER_SELECT);
2925 }
2926
2927 // scroll channel list down
2928 void multi_pxo_scroll_channels_down()
2929 {
2930         // if we're already at the tail of the list, do nothing
2931         if((Multi_pxo_channel_start == NULL) || (Multi_pxo_channel_start->next == Multi_pxo_channels)){
2932                 gamesnd_play_iface(SND_GENERAL_FAIL);
2933                 return;
2934         }
2935
2936         // if we can't scroll further without going past the end of the viewable list, don't
2937         if((Multi_pxo_channel_start_index + Multi_pxo_max_chan_display[gr_screen.res]) >= Multi_pxo_channel_count){
2938                 gamesnd_play_iface(SND_GENERAL_FAIL);
2939                 return;
2940         }
2941
2942         // otherwise move down one
2943         Multi_pxo_channel_start = Multi_pxo_channel_start->next;
2944         Multi_pxo_channel_start_index++;
2945         gamesnd_play_iface(SND_USER_SELECT);
2946 }
2947
2948 // attempt to join a channel
2949 void multi_pxo_join_channel(pxo_channel *chan)
2950 {       
2951         char switch_msg[256];
2952         
2953         // if we're already on this channel, do nothing
2954         if(ON_CHANNEL() && !SDL_strcasecmp(chan->name,Multi_pxo_channel_current.name)){
2955                 return;
2956         }
2957
2958         // if we're already trying to join a channel, do nothing
2959         if(SWITCHING_CHANNELS()){
2960                 return;
2961         }
2962
2963         // try and join the channel     
2964         switch(SetNewChatChannel(chan->name)){
2965         case -1 :
2966                 Int3();
2967                 break;
2968                 
2969         case 0 :
2970                 // decrement the count of our current channel
2971                 pxo_channel *lookup;
2972                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
2973                 if(lookup != NULL){
2974                         lookup->num_users--;
2975                 }
2976
2977                 // set our current channel as none
2978                 memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel));
2979                 Multi_pxo_channel_current.num_users = -1;
2980
2981                 multi_pxo_set_status_text(XSTR("Switching channels",953));
2982
2983                 // copy the channel
2984                 memcpy(&Multi_pxo_channel_switch,chan,sizeof(pxo_channel));
2985
2986                 // clear the player list
2987                 multi_pxo_clear_players();
2988
2989                 // display a line of text indicating that we're switching channels
2990                 memset(switch_msg,0,256);
2991
2992                 if(strlen(Multi_pxo_channel_switch.name) > 1){
2993                         sprintf(switch_msg, "[Switching to channel %s]", Multi_pxo_channel_switch.name + 1);
2994                 } else {
2995                         sprintf(switch_msg, "[Switching to channel %s]", Multi_pxo_channel_switch.name);
2996                 }
2997                 multi_pxo_chat_process_incoming(switch_msg, CHAT_MODE_CHANNEL_SWITCH);
2998                 break;
2999
3000         case 1 :
3001                 Int3();         
3002         }       
3003 }
3004
3005 // handle any processing details if we're currently trying to join a channel
3006 void multi_pxo_handle_channel_change()
3007 {                       
3008         // if we're not switching channels, do nothing
3009         if(!SWITCHING_CHANNELS()){
3010                 return;
3011         }
3012
3013         // if we are, check the status
3014         switch(SetNewChatChannel(NULL)){
3015         // failed to switch
3016         case -1 :
3017                 // unset our switching struct
3018                 memset(&Multi_pxo_channel_switch,0,sizeof(pxo_channel));
3019                 Multi_pxo_channel_switch.num_users = -1;
3020
3021                 // notify of error
3022                 multi_pxo_set_status_text(XSTR("No channel (error while switching)",954));
3023                 break;
3024
3025         // still switching
3026         case 0:
3027                 break;
3028
3029         // successfully changed
3030         case 1:
3031                 // copy the current channel info, and unset the switching status
3032                 memcpy(&Multi_pxo_channel_current,&Multi_pxo_channel_switch,sizeof(pxo_channel));
3033                 Multi_pxo_channel_switch.num_users = -1;
3034
3035                 // set our "last" channel
3036                 strcpy(Multi_pxo_channel_last, Multi_pxo_channel_current.name);
3037
3038                 // notify the user              
3039                 multi_pxo_set_status_text(XSTR("Connected to Parallax Online",951));
3040
3041                 // if we don't already have this guy on the list, add him
3042                 pxo_channel *lookup;
3043                 lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels);
3044                 if(lookup == NULL){
3045                         // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail
3046                         lookup = multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels);
3047                 }
3048
3049                 // set the user count to be 1 (just me)
3050                 if(lookup != NULL){
3051                         lookup->num_users = 1;
3052                 }
3053
3054                 // clear the chat area
3055                 // multi_pxo_chat_clear();              
3056
3057                 // set the "switch" delay timestamp
3058                 Multi_pxo_switch_delay = timestamp(MULTI_PXO_SWITCH_DELAY_TIME);
3059
3060                 // refresh current channel server count
3061                 multi_pxo_channel_refresh_current();            
3062                 break;
3063         }
3064 }
3065
3066
3067 // player related stuff -------------------------------------------
3068
3069 // clear the old player list
3070 void multi_pxo_clear_players()
3071 {
3072         player_list *moveup,*backup;
3073         
3074         // if the list is null, don't free it up
3075         if(Multi_pxo_players != NULL){          
3076                 // otherwise
3077                 moveup = Multi_pxo_players;
3078                 backup = NULL;
3079                 if(moveup != NULL){
3080                         do {                    
3081                                 backup = moveup;
3082                                 moveup = moveup->next;                  
3083                 
3084                                 // free the struct itself
3085                                 free(backup);
3086                                 backup = NULL;
3087                         } while(moveup != Multi_pxo_players);
3088                         Multi_pxo_players = NULL;
3089                 }       
3090         }
3091
3092         Multi_pxo_player_start = NULL;  
3093         // Multi_pxo_player_start_index = -1;
3094         Multi_pxo_player_select = NULL;
3095
3096         // reset the slider
3097         // Multi_pxo_player_slider.set_numberItems(0);
3098
3099         // add a bunch of bogus players
3100         /*
3101         char str[50];
3102         for(int idx=0;idx<30;idx++){
3103                 sprintf(str,"player%d",idx);
3104                 multi_pxo_add_player(str);
3105         }
3106         */
3107 }
3108
3109 // create a new player with the given name and place it on the player list, return a pointer or NULL on fail
3110 player_list *multi_pxo_add_player(char *name)
3111 {
3112         player_list *new_player;
3113
3114         // try and allocate a new player_list struct
3115         new_player = (player_list *)malloc(sizeof(player_list));
3116         if ( new_player == NULL ) {
3117                 nprintf(("Network", "Cannot allocate space for new player_list structure\n"));
3118                 return NULL;
3119         }       
3120         // try and allocate a string for the channel name
3121         strncpy(new_player->name, name, MAX_PLAYER_NAME_LEN);   
3122
3123         // insert it on the list
3124         if ( Multi_pxo_players != NULL ) {
3125                 new_player->next = Multi_pxo_players->next;
3126                 new_player->next->prev = new_player;
3127                 Multi_pxo_players->next = new_player;
3128                 new_player->prev = Multi_pxo_players;           
3129         } else {
3130                 Multi_pxo_players = new_player;
3131                 Multi_pxo_players->next = Multi_pxo_players->prev = Multi_pxo_players;
3132         }
3133
3134         // increment the start position
3135         if(Multi_pxo_player_start != NULL){
3136                 // Multi_pxo_player_start_index++;
3137         }
3138
3139         // new player
3140         Multi_pxo_player_count++;
3141
3142         // update the count on the slider
3143         // Multi_pxo_player_slider.set_numberItems(Multi_pxo_player_count);
3144                 
3145         return new_player;
3146 }
3147
3148 // remove a player with the given name
3149 void multi_pxo_del_player(char *name)
3150 {
3151         player_list *lookup;
3152
3153         // try and find this guy
3154         lookup = Multi_pxo_players;
3155         if(lookup == NULL){
3156                 return;
3157         }
3158         do {
3159                 // if we found a match, delete it
3160                 if(!SDL_strcasecmp(name,lookup->name)){
3161                         // if this is the only item on the list, free stuff up
3162                         if(lookup->next == lookup){
3163                                 SDL_assert(lookup == Multi_pxo_players);
3164                                 free(lookup);
3165                                 Multi_pxo_players = NULL;
3166                                 multi_pxo_clear_players();
3167                         }
3168                         // otherwise, just delete it 
3169                         else {
3170                                 lookup->next->prev = lookup->prev;
3171                                 lookup->prev->next = lookup->next;                              
3172                                 
3173                                 // if this was our selected item, unselect it
3174                                 if((lookup == Multi_pxo_player_select) && (Multi_pxo_player_select != NULL)){
3175                                         Multi_pxo_player_select = Multi_pxo_player_select->next;
3176                                 }
3177
3178                                 // if this was our point to start viewing from, select another
3179                                 if(lookup == Multi_pxo_player_start){
3180                                         // if this is the head of the list, move up one
3181                                         if(Multi_pxo_players == lookup){
3182                                                 Multi_pxo_player_start = Multi_pxo_player_start->next;
3183                                                 // Multi_pxo_player_start_index = 0;
3184                                         }
3185                                         // otherwise move back
3186                                         else {
3187                                                 Multi_pxo_player_start = Multi_pxo_player_start->prev;
3188                                                 // Multi_pxo_player_start_index++;
3189                                         }
3190                                 }
3191
3192                                 // if this is the head of the list, move it up
3193                                 if(lookup == Multi_pxo_players){
3194                                         Multi_pxo_players = Multi_pxo_players->next;                                    
3195                                 }
3196
3197                                 // free the item up
3198                                 lookup->next = NULL;
3199                                 lookup->prev = NULL;
3200                                 free(lookup);
3201                         }       
3202
3203                         // new player
3204                         Multi_pxo_player_count--;
3205                         SDL_assert(Multi_pxo_player_count >= 0);
3206
3207                         // update the count on the slider
3208                         // Multi_pxo_player_slider.set_numberItems(Multi_pxo_player_count);
3209                         // Multi_pxo_player_slider.force_currentItem(Multi_pxo_player_start_index);
3210                                 
3211                         // we're done now
3212                         return;
3213                 }
3214
3215                 // next item
3216                 lookup = lookup->next;
3217         } while((lookup != NULL) && (lookup != Multi_pxo_players));
3218 }
3219
3220 // try and find a player with the given name, return a pointer to his entry (or NULL)
3221 player_list *multi_pxo_find_player(char *name)
3222 {
3223         player_list *lookup;
3224
3225         // look through all players
3226         lookup = Multi_pxo_players;
3227         if(lookup == NULL){
3228                 return NULL;
3229         } 
3230         do {    
3231                 if(!SDL_strcasecmp(name,lookup->name)){
3232                         return lookup;
3233                 }
3234
3235                 lookup = lookup->next;
3236         } while((lookup != NULL) && (lookup != Multi_pxo_players));
3237
3238         // return NULL
3239         return NULL;
3240 }
3241
3242 // process the player list (select, etc)
3243 void multi_pxo_process_players()
3244 {
3245         int item_index,my;
3246         player_list *lookup;
3247         
3248         // if we don't have a start item, but the list is non-null
3249         if((Multi_pxo_player_start == NULL) && (Multi_pxo_players != NULL)){
3250                 Multi_pxo_player_start = Multi_pxo_players;             
3251                 // Multi_pxo_player_start_index = 0;
3252
3253                 // update the slider
3254                 // Multi_pxo_player_slider.set_currentItem(Multi_pxo_player_start_index);
3255         } 
3256
3257         // if we don't have a selected item, but the list is non-null
3258         if((Multi_pxo_player_select == NULL) && (Multi_pxo_players != NULL)){
3259                 Multi_pxo_player_select = Multi_pxo_players;
3260         }
3261
3262         // see if we have a mouse click on the channel region
3263         if(Multi_pxo_player_button.pressed()){
3264                 Multi_pxo_player_button.get_mouse_pos(NULL,&my);
3265
3266                 // index from the top
3267                 item_index = my / 10;
3268
3269                 // select the item if possible
3270                 lookup = Multi_pxo_player_start;
3271                 if(lookup == NULL){
3272                         return;
3273                 }
3274                 // select item 0
3275                 if(item_index == 0){
3276                         Multi_pxo_player_select = Multi_pxo_player_start;
3277                         return;
3278                 }
3279                 do {
3280                         // move to the next item
3281                         lookup = lookup->next;
3282                         item_index--;
3283
3284                         // if this item is our guy
3285                         if((item_index == 0) && (lookup != Multi_pxo_players)){
3286                                 Multi_pxo_player_select = lookup;
3287                                 return;
3288                         }
3289                 } while((lookup != Multi_pxo_players) && (item_index > 0));             
3290         }
3291 }
3292
3293 // display the player list
3294 void multi_pxo_blit_players()
3295 {
3296         player_list *moveup;
3297         char player_name[255];
3298         int disp_count,y_start;
3299
3300         // blit as many channels as we can
3301         disp_count = 0;
3302         y_start = Multi_pxo_player_coords[gr_screen.res][1];
3303         moveup = Multi_pxo_player_start;
3304         if(moveup == NULL){
3305                 return;
3306         }
3307         do {
3308                 // if this is the currently selected item, highlight it
3309                 if(moveup == Multi_pxo_player_select){
3310                         gr_set_color_fast(&Color_bright);
3311                 }
3312                 // otherwise draw it normally
3313                 else {
3314                         gr_set_color_fast(&Color_normal);
3315                 }
3316
3317                 // make sure the string fits            
3318                 strcpy(player_name,moveup->name);               
3319                 gr_force_fit_string(player_name, 254, Multi_pxo_player_coords[gr_screen.res][2]);
3320
3321                 // blit the string
3322                 gr_string(Multi_pxo_player_coords[gr_screen.res][0], y_start, player_name);
3323
3324                 // increment the displayed count
3325                 disp_count++;
3326                 y_start += 10;
3327
3328                 // next item
3329                 moveup = moveup->next;
3330         } while((moveup != Multi_pxo_players) && (disp_count < Multi_pxo_max_player_display[gr_screen.res]));
3331 }
3332
3333 // scroll player list up
3334 void multi_pxo_scroll_players_up()
3335 {
3336         // if we're already at the head of the list, do nothing
3337         if((Multi_pxo_player_start == NULL) || (Multi_pxo_player_start == Multi_pxo_players)){
3338                 gamesnd_play_iface(SND_GENERAL_FAIL);           
3339                 return;
3340         }
3341         
3342         // otherwise move up one
3343         Multi_pxo_player_start = Multi_pxo_player_start->prev;  
3344         // Multi_pxo_player_start_index--;
3345         // SDL_assert(Multi_pxo_player_start_index >= 0);
3346
3347         gamesnd_play_iface(SND_USER_SELECT);
3348 }
3349
3350 // scroll player list down
3351 void multi_pxo_scroll_players_down()
3352 {
3353         player_list *lookup;
3354         int count = 0;
3355         
3356         // see if its okay to scroll down
3357         lookup = Multi_pxo_player_start;
3358         if(lookup == NULL ){
3359                 gamesnd_play_iface(SND_GENERAL_FAIL);
3360                 return;
3361         }
3362         count = 0;
3363         while(lookup->next != Multi_pxo_players){
3364                 lookup = lookup->next;
3365                 count++;
3366         }
3367         
3368         // if we can move down
3369         if(count >= Multi_pxo_max_player_display[gr_screen.res]){
3370                 Multi_pxo_player_start = Multi_pxo_player_start->next;
3371
3372                 // Multi_pxo_player_start_index++;
3373
3374                 gamesnd_play_iface(SND_USER_SELECT);
3375         } else {
3376                 gamesnd_play_iface(SND_GENERAL_FAIL);
3377         }       
3378 }
3379
3380
3381 // chat text stuff -----------------------------------------
3382
3383 // initialize and create the chat text linked list
3384 void multi_pxo_chat_init()
3385 {
3386         int idx;
3387         chat_line *new_line;
3388
3389         // no chat lines
3390         Multi_pxo_chat = NULL;
3391         Multi_pxo_chat_add = NULL;
3392         Multi_pxo_chat_start = NULL;
3393         Multi_pxo_chat_start_index = -1;
3394
3395         // create the lines in a non-circular doubly linked list
3396         for(idx=0;idx<MAX_CHAT_LINES;idx++){
3397                 new_line = (chat_line*)malloc(sizeof(chat_line));       
3398                 
3399                 // clear the line out
3400                 SDL_assert(new_line != NULL);
3401                 if(new_line == NULL){
3402                         return;
3403                 }
3404                 memset(new_line,0,sizeof(chat_line));
3405                 new_line->prev = NULL;
3406                 new_line->next = NULL;          
3407
3408                 // insert it into the (empty) list
3409                 if(Multi_pxo_chat == NULL){
3410                         Multi_pxo_chat = new_line;
3411                 }
3412                 // insert it onto the (non-empty) list
3413                 else {
3414                         Multi_pxo_chat->prev = new_line;
3415                         new_line->next = Multi_pxo_chat;
3416                         Multi_pxo_chat = new_line;
3417                 }
3418         }
3419
3420         // start adding chat lines at the beginning of the list
3421         Multi_pxo_chat_add = Multi_pxo_chat;
3422 }
3423
3424 // free up all chat list stuff
3425 void multi_pxo_chat_free()
3426 {
3427         chat_line *moveup, *backup;     
3428
3429         // free all items up
3430         moveup = Multi_pxo_chat;
3431         while(moveup != NULL){
3432                 backup = moveup;                
3433                 moveup = moveup->next;
3434
3435                 free(backup);
3436         }
3437
3438         // no chat lines
3439         Multi_pxo_chat = NULL;
3440         Multi_pxo_chat_add = NULL;
3441         Multi_pxo_chat_start = NULL;
3442         Multi_pxo_chat_start_index = -1;
3443         Multi_pxo_chat_count = 0;
3444         Multi_pxo_chat_slider.set_numberItems(0);       
3445 }
3446
3447 // clear all lines of chat text in the chat area
3448 void multi_pxo_chat_clear()
3449 {
3450         chat_line *moveup;
3451
3452         // clear the text in all the lines
3453         moveup = Multi_pxo_chat;
3454         while(moveup != NULL){
3455                 memset(moveup->text,0,MAX_CHAT_LINE_LEN+1);
3456                 moveup = moveup->next;
3457         }
3458
3459         // how many chat lines we have
3460         Multi_pxo_chat_count = 0;
3461
3462         // start adding chat lines at the beginning of the list
3463         Multi_pxo_chat_add = Multi_pxo_chat;
3464 }
3465
3466 // add a line of text
3467 void multi_pxo_chat_add_line(char *txt, int mode)
3468 {
3469         chat_line *temp;
3470         
3471         // copy in the text
3472         SDL_assert(Multi_pxo_chat_add != NULL);
3473         strncpy(Multi_pxo_chat_add->text, txt, MAX_CHAT_LINE_LEN);
3474         Multi_pxo_chat_add->mode = mode;
3475
3476         // if we're at the end of the list, move the front item down
3477         if(Multi_pxo_chat_add->next == NULL) {
3478                 // store the new "head" of the list
3479                 temp = Multi_pxo_chat->next;
3480
3481                 // move the current head to the end of the list
3482                 Multi_pxo_chat_add->next = Multi_pxo_chat;
3483                 temp->prev = NULL;              
3484                 Multi_pxo_chat->prev = Multi_pxo_chat_add;
3485                 Multi_pxo_chat->next = NULL;
3486
3487                 // reset the head of the list
3488                 Multi_pxo_chat = temp;
3489
3490                 // set the new add line
3491                 Multi_pxo_chat_add = Multi_pxo_chat_add->next;
3492                 memset(Multi_pxo_chat_add->text, 0, MAX_CHAT_LINE_LEN+1);
3493                 Multi_pxo_chat_add->mode = CHAT_MODE_NORMAL;
3494         } 
3495         // if we're not at the end of the list, just move up by one
3496         else {
3497                 // set the new add line
3498                 Multi_pxo_chat_add = Multi_pxo_chat_add->next;
3499         }
3500
3501         // if we've reached max chat lines, don't increment
3502         if(Multi_pxo_chat_count < MAX_CHAT_LINES) {
3503                 Multi_pxo_chat_count++;
3504         }
3505
3506         // set the count
3507         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
3508
3509         // force the position, in case we arent at the bottom of the list
3510
3511
3512         // move to the bottom of the chat area
3513         /*
3514         if(from_self){
3515                 multi_pxo_goto_bottom();
3516         }       
3517         else {          
3518                 // if we have more than the # of lines displayable
3519                 if(Multi_pxo_chat_count >= MULTI_PXO_MAX_CHAT_DISPLAY){
3520                 }
3521                 multi_pxo_goto_bottom();
3522         }
3523         */
3524         multi_pxo_goto_bottom();
3525 }
3526
3527 // process an incoming line of text
3528 void multi_pxo_chat_process_incoming(const char *txt,int mode)
3529 {
3530         char msg_total[512],line[512];
3531         int     n_lines,idx;
3532         int     n_chars[20];
3533         char    *p_str[20];                     //  the initial line (unindented)       
3534         const char *priv_ptr = NULL;
3535
3536         // filter out "has left" channel messages, when switching channels
3537         if((SWITCHING_CHANNELS() || ((Multi_pxo_switch_delay != -1) && !timestamp_elapsed(Multi_pxo_switch_delay))) && 
3538                 multi_pxo_chat_is_left_message(txt)){
3539                 return;
3540         }
3541                 
3542         // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL
3543         priv_ptr = multi_pxo_chat_is_private(txt);
3544         if(priv_ptr != NULL){           
3545                 strcpy(msg_total, priv_ptr);
3546         } else {
3547                 strcpy(msg_total, txt);
3548         }       
3549
3550         // determine what mode to display this text in
3551
3552         // if this is private chat
3553         if(priv_ptr != NULL){
3554                 mode = CHAT_MODE_PRIVATE;
3555         }
3556         // all other chat
3557         else {
3558                 // if this is a server message
3559                 if(multi_pxo_is_server_text(txt)){
3560                         mode = CHAT_MODE_SERVER;
3561                 }
3562                 // if this is a MOTD
3563                 else if(multi_pxo_is_motd_text(txt)){
3564                         // mode = CHAT_MODE_MOTD;
3565                         // stuff the motd
3566                         multi_pxo_motd_add_text(txt);
3567                         return;
3568                 } 
3569                 // if this is the end of motd text
3570                 else if(multi_pxo_is_end_of_motd_text(txt)){
3571                         multi_pxo_set_end_of_motd();
3572                         return;
3573                 }
3574         }
3575
3576         // split the text up into as many lines as necessary
3577         n_lines = split_str(msg_total, Multi_pxo_chat_coords[gr_screen.res][2] - 5, n_chars, p_str, 3);
3578         SDL_assert((n_lines != -1) && (n_lines <= 20));
3579         if((n_lines < 0) || (n_lines > 20)) {
3580                 return;
3581         }
3582
3583         // if the string fits on one line
3584         if(n_lines == 1) {
3585                 multi_pxo_chat_add_line(msg_total,mode);
3586
3587                 // don't pad with extra spaces if its from the server
3588                 /*
3589                 if(mode != CHAT_MODE_SERVER){
3590                         multi_pxo_chat_add_line("",CHAT_MODE_NORMAL);
3591                 }
3592                 */
3593         }
3594         // if the string was split into multiple lines
3595         else {
3596                 // add the first line           
3597                 memcpy(line,p_str[0],n_chars[0]);
3598                 line[n_chars[0]] = '\0';
3599                 multi_pxo_chat_add_line(line,mode);
3600
3601                 // copy the rest of the lines
3602                 for(idx=1; idx<n_lines; idx++){
3603                         memcpy(line,p_str[idx],n_chars[idx]);
3604                         line[n_chars[idx]] = '\0';                      
3605                         
3606                         // unless the current mode is server or "switching channels", make all these CHAT_MODE_CARRY
3607                         if((mode != CHAT_MODE_SERVER) && (mode != CHAT_MODE_CHANNEL_SWITCH)){
3608                                 mode = CHAT_MODE_CARRY;
3609                         }                       
3610                         multi_pxo_chat_add_line(line, mode);
3611                 }
3612         }       
3613 }
3614
3615 // blit the chat text
3616 void multi_pxo_chat_blit()
3617 {
3618         int y_start;
3619         int disp_count,token_width;
3620         char piece[100];
3621         char title[255];
3622         char *tok;
3623         chat_line *moveup;
3624
3625         // blit the title line
3626         memset(title,0,15);
3627         if(ON_CHANNEL()){
3628                 if(strlen(Multi_pxo_channel_current.name) > 1){
3629                         sprintf(title, XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name+1);  // [[ <who> on <channel> ]]
3630                 } else {
3631                         sprintf(title, XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name);    // [[ <who> on <channel> ]]
3632                 }
3633         } else {
3634                 strcpy(title,XSTR("Parallax Online - No Channel", 956));
3635         }       
3636         gr_force_fit_string(title, 254, Multi_pxo_chat_coords[gr_screen.res][2] - 10);
3637         gr_get_string_size(&token_width,NULL,title);
3638         gr_set_color_fast(&Color_normal);
3639         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); 
3640
3641         // blit all active lines of text
3642         moveup = Multi_pxo_chat_start;  
3643         disp_count = 0;
3644         y_start = Multi_pxo_chat_coords[gr_screen.res][1];
3645         while((moveup != NULL) && (moveup != Multi_pxo_chat_add) && (disp_count < (Multi_pxo_max_chat_display[gr_screen.res]))){
3646                 switch(moveup->mode){
3647                 // if this is text from the server, display it all "bright"
3648                 case CHAT_MODE_SERVER:                          
3649                         gr_set_color_fast(&Color_bright);
3650                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3651                         break;
3652
3653                 // if this is motd, display it all "bright"
3654                 case CHAT_MODE_MOTD:
3655                         gr_set_color_fast(&Color_bright_white);
3656                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3657                         break;
3658
3659                 // normal mode, just highlight the server
3660                 case CHAT_MODE_PRIVATE:         
3661                 case CHAT_MODE_NORMAL:                                  
3662                         strcpy(piece,moveup->text);
3663                         tok = strtok(piece," ");
3664                         if(tok != NULL){
3665                                 // get the width of just the first "piece"
3666                                 gr_get_string_size(&token_width, NULL, tok);
3667                                 
3668                                 // draw it brightly
3669                                 gr_set_color_fast(&Color_bright);
3670                                 gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, tok);
3671
3672                                 // draw the rest of the string normally
3673                                 tok = strtok(NULL,"");
3674                                 if(tok != NULL){
3675                                         gr_set_color_fast(&Color_normal);
3676                                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0] + token_width + 6, y_start, tok);
3677                                 }
3678                         }
3679                         break;
3680                 
3681                 // carry mode, display with no highlight
3682                 case CHAT_MODE_CARRY:
3683                         gr_set_color_fast(&Color_normal);
3684                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3685                         break;
3686
3687                 // "switching channels mode", display it bright
3688                 case CHAT_MODE_CHANNEL_SWITCH:
3689                         gr_set_color_fast(&Color_bright);
3690                         gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text);
3691                         break;
3692                 }
3693                 
3694                 // next chat line
3695                 moveup = moveup->next;
3696                 disp_count++;
3697                 y_start += 10;
3698         }
3699
3700         if ((moveup != Multi_pxo_chat_add) && (moveup != NULL)) {
3701                 Can_scroll_down = 1;
3702         } else {
3703                 Can_scroll_down = 0;
3704         }
3705 }
3706
3707 // scroll to the very bottom of the chat area
3708 void multi_pxo_goto_bottom()
3709 {
3710         chat_line *backup;
3711         int idx;
3712
3713         if (Multi_pxo_chat == NULL) {
3714                 return;
3715         }
3716         
3717         // if we have less than the displayable amount of lines, do nothing
3718         if(Multi_pxo_chat_count <= Multi_pxo_max_chat_display[gr_screen.res]){
3719                 Multi_pxo_chat_start = Multi_pxo_chat;                                          
3720                 
3721                 // nothing to do for the slider
3722                 Multi_pxo_chat_slider.set_numberItems(0);
3723                 return;
3724         }
3725
3726         if (!Can_scroll_down)
3727         {
3728                 // otherwise move back the right # of items
3729                 backup = Multi_pxo_chat_add;    
3730                 for(idx=0; idx<Multi_pxo_max_chat_display[gr_screen.res]; idx++){
3731                         SDL_assert(backup->prev != NULL);
3732                         backup = backup->prev;          
3733                 }
3734
3735                 Multi_pxo_chat_start = backup;
3736
3737                 // fixup the start index
3738                 multi_pxo_chat_adjust_start();  
3739         }
3740 }
3741
3742 // scroll the text up
3743 void multi_pxo_scroll_chat_up()
3744 {
3745         // if we're already at the top of the list, don't do anything   
3746         if ((Multi_pxo_chat_start == NULL) || (Multi_pxo_chat_start == Multi_pxo_chat)) {
3747                 gamesnd_play_iface(SND_GENERAL_FAIL);
3748                 return;
3749         }
3750
3751         // otherwise move up one
3752         Multi_pxo_chat_start = Multi_pxo_chat_start->prev;      
3753
3754         multi_pxo_chat_adjust_start();  
3755         
3756         gamesnd_play_iface(SND_USER_SELECT);
3757 }
3758
3759 // returns 1 if we can scroll down, 0 otherwise
3760 int multi_pxo_can_scroll_down()
3761 {
3762         chat_line *lookup;
3763         int count = 0;
3764         
3765         // see if its okay to scroll down
3766         lookup = Multi_pxo_chat_start;
3767         if (lookup == NULL) {
3768         //      gamesnd_play_iface(SND_GENERAL_FAIL);
3769                 return 0;
3770         }
3771         count = 0;
3772         while (lookup != Multi_pxo_chat_add) {
3773                 lookup = lookup->next;
3774                 count++;
3775         }
3776         
3777         // check if we can move down, return accordingly
3778         if (count > Multi_pxo_max_chat_display[gr_screen.res]) {
3779                 return 1;
3780         } else {
3781                 return 0;
3782         }
3783 }
3784
3785 // scroll the text down
3786 void multi_pxo_scroll_chat_down()
3787 {
3788         // if we can move down
3789         if (multi_pxo_can_scroll_down()) {
3790                 Multi_pxo_chat_start = Multi_pxo_chat_start->next;              
3791                 multi_pxo_chat_adjust_start();  
3792                 gamesnd_play_iface(SND_USER_SELECT);
3793         } else {
3794                 gamesnd_play_iface(SND_GENERAL_FAIL);
3795         }
3796 }
3797
3798 // process chat controls
3799 void multi_pxo_chat_process()
3800 {
3801         char *remainder = NULL;
3802         const char *result = NULL;
3803         char msg[512];
3804         int msg_pixel_width;
3805
3806         // if the chat line is getting too long, fire off the message, putting the last
3807         // word on the next input line.
3808         memset(msg, 0, 512);
3809         Multi_pxo_chat_input.get_text(msg);
3810
3811         // determine if the width of the string in pixels is > than the inputbox width -- if so,
3812         // then send the message
3813         gr_get_string_size(&msg_pixel_width, NULL, msg);
3814         // if ( msg_pixel_width >= (Chatbox_inputbox_w - Player->short_callsign_width) ) {
3815         if ( msg_pixel_width >= (Multi_pxo_input_coords[gr_screen.res][2])) {
3816                 remainder = strrchr(msg, ' ');
3817                 if ( remainder ) {
3818                         *remainder = '\0';
3819                         remainder++;
3820                 }       
3821                 
3822                 // if we're connected to a channel, send the chat to the server
3823                 if(ON_CHANNEL()){
3824                         result = SendChatString(msg,1);
3825                         if(result != NULL){
3826                                 multi_pxo_chat_process_incoming(result);
3827                         }
3828
3829                         // display any remainder of text on the next line
3830                         Multi_pxo_chat_input.set_text( remainder ? remainder : "" );
3831                 } else {
3832                         Multi_pxo_chat_input.set_text("");
3833                 }
3834         } else if((Multi_pxo_chat_input.pressed() && (strlen(msg) > 0)) || (strlen(msg) >= MAX_CHAT_LINE_LEN)) { 
3835                 // tack on the null terminator in the boundary case
3836                 int x = strlen(msg);
3837                 if(x >= MAX_CHAT_LINE_LEN){
3838                         msg[MAX_CHAT_LINE_LEN-1] = '\0';
3839                 }               
3840
3841                 // ignore "/nick" commands
3842                 if(multi_pxo_is_nick_command(msg)){
3843                         Multi_pxo_chat_input.set_text("");
3844                         return;
3845                 }
3846                 
3847                 // send the chat to the server                  
3848                 // if we're connected to a channel, send the chat to the server
3849                 if(ON_CHANNEL()){               
3850                         result = SendChatString(msg,1);
3851                         if(result != NULL){
3852                                 multi_pxo_chat_process_incoming(result);
3853                         }
3854
3855                         // display any remainder of text on the next line
3856                         Multi_pxo_chat_input.set_text( remainder ? remainder : "" );
3857                 } else {
3858                         Multi_pxo_chat_input.set_text("");
3859                 }
3860         }       
3861 }
3862
3863 // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL
3864 //XSTR:OFF
3865
3866 // NOTE : DO NOT LOCALIZE THESE STRINGS!!!! THEY ARE CONSTANTS WHICH ARE CHECKED AGAINST 
3867 // PXO CHAT SERVER DATA. THEY CANNOT CHANGE!!!
3868 #define PMSG_FROM                       "private message from "
3869 #define PMSG_TO                 "private message to "
3870 const char *multi_pxo_chat_is_private(const char *txt)
3871 {
3872         // quick check
3873         if( strlen(txt) > strlen( PMSG_FROM ) ){        
3874                 // otherwise do a comparison
3875                 if(!SDL_strncasecmp( txt, PMSG_FROM, strlen(PMSG_FROM) )){
3876                         return &txt[strlen( PMSG_FROM )];
3877                 } 
3878         }
3879
3880         // quick check
3881         if(strlen(txt) > strlen( PMSG_TO )){    
3882                 // otherwise do a comparison
3883                 if(!SDL_strncasecmp(txt,PMSG_TO,strlen(PMSG_TO))){
3884                         return &txt[strlen(PMSG_TO)];
3885                 } 
3886         }
3887         
3888         return NULL;
3889 }
3890 //XSTR:ON
3891
3892 // if the text came from the server
3893 int multi_pxo_is_server_text(const char *txt)
3894 {               
3895         // if the message is prefaced by a ***
3896         if((strlen(txt) >= strlen(MULTI_PXO_SERVER_PREFIX)) && !strncmp(txt, MULTI_PXO_SERVER_PREFIX, strlen(MULTI_PXO_SERVER_PREFIX))){
3897                 return 1;
3898         }
3899
3900         return 0;
3901 }
3902
3903 // if the text is message of the day text
3904 int multi_pxo_is_motd_text(const char *txt)
3905 {
3906         // if we're not on a channel, and this is not a channel switching message assume its coming from a server
3907         if((strlen(txt) >= strlen(PXO_CHAT_MOTD_PREFIX)) && !strncmp(txt, PXO_CHAT_MOTD_PREFIX, strlen(PXO_CHAT_MOTD_PREFIX))){
3908                 return 1;
3909         }       
3910         
3911         return 0;
3912 }
3913
3914 // if the text is the end of motd text
3915 int multi_pxo_is_end_of_motd_text(const char *txt)
3916 {
3917         // if we're not on a channel, and this is not a channel switching message assume its coming from a server
3918         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))){
3919                 return 1;
3920         }       
3921         
3922         return 0;
3923 }
3924
3925 // if the text is a "has left message" from the server
3926 int multi_pxo_chat_is_left_message(const char *txt)
3927 {
3928         char last_portion[100];
3929         
3930         // if the text is not server text
3931         if(!multi_pxo_is_server_text(txt)){
3932                 return 0;
3933         }
3934
3935         // check to see if the last portion is the correct wording
3936         memset(last_portion, 0, 100);
3937         if((strlen(txt) > strlen(MULTI_PXO_HAS_LEFT)) && !strcmp(&txt[strlen(txt) - strlen(MULTI_PXO_HAS_LEFT)], MULTI_PXO_HAS_LEFT)){
3938                 return 1;
3939         }
3940
3941         // check the end of the line
3942         return 0;
3943 }
3944
3945 // recalculate the chat start index, and adjust the slider properly
3946 void multi_pxo_chat_adjust_start()
3947 {
3948         chat_line *moveup;
3949
3950         // if we have no chat
3951         if (Multi_pxo_chat == NULL) {
3952                 Multi_pxo_chat_start_index = -1;                
3953                 return;
3954         }
3955
3956         // traverse
3957         Multi_pxo_chat_start_index = 0;
3958         moveup = Multi_pxo_chat;
3959         while((moveup != Multi_pxo_chat_start) && (moveup != NULL)){
3960                 Multi_pxo_chat_start_index++;
3961                 moveup = moveup->next;
3962         }
3963
3964         // set the slider index
3965         Multi_pxo_chat_slider.force_currentItem(Multi_pxo_chat_start_index);
3966 }
3967
3968 // motd stuff ---------------------------------------------------------
3969
3970 // initialize motd when going into this screen
3971 void multi_pxo_motd_init()
3972 {
3973         // zero the motd string
3974         strcpy(Pxo_motd, "");
3975
3976         // haven't gotten it yet
3977         Pxo_motd_end = 0;
3978
3979         // haven't read it yet either
3980         Pxo_motd_read = 0;
3981 }
3982
3983 // set the motd text
3984 void multi_pxo_motd_add_text(const char *text)
3985 {
3986         int cur_len = strlen(Pxo_motd);
3987         int new_len;
3988
3989         // sanity
3990         if(text == NULL){
3991                 return;
3992         }
3993
3994         // make sure its motd text
3995         SDL_assert(multi_pxo_is_motd_text(text));
3996         if(!multi_pxo_is_motd_text(text)){
3997                 return;
3998         }
3999         
4000         // if its a 0 line motd
4001         if(strlen(text) <= strlen(PXO_CHAT_MOTD_PREFIX)){
4002                 return;
4003         }
4004
4005         // add text to the motd
4006         new_len = strlen(text + strlen(PXO_CHAT_MOTD_PREFIX)) - 1;
4007         if((cur_len + new_len + 1) < MAX_PXO_MOTD_LEN){
4008                 strcat(Pxo_motd, text + strlen(PXO_CHAT_MOTD_PREFIX) + 1);
4009                 strcat(Pxo_motd, "\n");
4010                 mprintf(("MOTD ADD : %s\n", Pxo_motd));
4011         }
4012 }
4013
4014 // set end of motd
4015 void multi_pxo_set_end_of_motd()
4016 {
4017         int blink = 1;
4018
4019         Pxo_motd_end = 1;
4020         mprintf(("MOTD ALL : %s\n", Pxo_motd));
4021         
4022         Pxo_motd_read = 0;
4023
4024         // do we have an old MOTD file laying around? If so, read it in and see if its the same
4025         unsigned int old_chksum;
4026         unsigned long new_chksum;
4027
4028         // checksum the current motd            
4029         new_chksum = cf_add_chksum_long(0, Pxo_motd, strlen(Pxo_motd));         
4030
4031         // checksum the old motd if its lying around
4032         CFILE *in = cfopen("oldmotd.txt", "rb");
4033         if(in != NULL){
4034                 // read the old checksum
4035                 cfread(&old_chksum, sizeof(old_chksum), 1, in);
4036                 cfclose(in);
4037                 
4038                 // same checksum? no blink
4039                 if(new_chksum == old_chksum){
4040                         blink = 0;
4041                 }
4042         }       
4043         
4044         // write out the motd for next time
4045         if(strlen(Pxo_motd)){
4046                 CFILE *out = cfopen("oldmotd.txt", "wb", CFILE_NORMAL, CF_TYPE_DATA);
4047                 if(out != NULL){
4048                         // write all the text
4049                         cfwrite(&new_chksum, sizeof(new_chksum), 1, out);
4050                         
4051                         // close the outfile
4052                         cfclose(out);
4053                 }
4054         }
4055         
4056         // set the blink stamp
4057         Pxo_motd_blink_stamp = -1;
4058         if(blink){              
4059                 Pxo_motd_blink_on = 0;
4060                 if(!Pxo_motd_blinked_already){
4061                         Pxo_motd_blink_stamp = timestamp(PXO_MOTD_BLINK_TIME);
4062                         Pxo_motd_blink_on = 1;
4063                 }
4064         }
4065
4066         Pxo_motd_blinked_already = 1;
4067 }
4068
4069 // display the motd dialog
4070 void multi_pxo_motd_dialog()
4071 {
4072         // mark the motd as read
4073         Pxo_motd_read = 1;
4074
4075         // simple popup, with a slider
4076         popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, Pxo_motd);
4077 }
4078
4079 // call to maybe blink the motd button
4080 void multi_pxo_motd_maybe_blit()
4081 {
4082         // if we got the end of the motd, and he hasn't read it yet
4083         if(Pxo_motd_end && !Pxo_motd_read && (Pxo_motd_blink_stamp != -1)){
4084                 // if the timestamp elapsed, flip the blink flag
4085                 if(timestamp_elapsed(Pxo_motd_blink_stamp)){
4086                         Pxo_motd_blink_on = !Pxo_motd_blink_on;
4087                         Pxo_motd_blink_stamp = timestamp(PXO_MOTD_BLINK_TIME);
4088                 }
4089
4090                 // draw properly
4091                 if(Pxo_motd_blink_on){
4092                         Multi_pxo_buttons[gr_screen.res][MULTI_PXO_MOTD].button.draw_forced(2);
4093                 }
4094         }       
4095 }
4096
4097
4098 // common dialog stuff ------------------------------------------------
4099
4100 int Multi_pxo_searching = 0; 
4101
4102 // initialize the common dialog with the passed max input length
4103 void multi_pxo_com_init(int input_len)
4104 {
4105         int idx;
4106         
4107         // create the interface window
4108         Multi_pxo_com_window.create(0, 0, gr_screen.max_w,gr_screen.max_h, 0);
4109         Multi_pxo_com_window.set_mask_bmap(Multi_pxo_com_mask_fname[gr_screen.res]);    
4110
4111         // create the interface buttons
4112         for(idx=0; idx<MULTI_PXO_COM_NUM_BUTTONS; idx++){
4113                 // create the object
4114                 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);
4115
4116                 // set the sound to play when highlighted
4117                 Multi_pxo_com_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
4118
4119                 // set the ani for the button
4120                 Multi_pxo_com_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_com_buttons[gr_screen.res][idx].filename);
4121
4122                 // set the hotspot
4123                 Multi_pxo_com_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_com_buttons[gr_screen.res][idx].hotspot);
4124         }                       
4125
4126         // add xstrs
4127         for(idx=0; idx<MULTI_PXO_COM_NUM_TEXT; idx++){
4128                 Multi_pxo_com_window.add_XSTR(&Multi_pxo_com_text[gr_screen.res][idx]);
4129         }
4130
4131         // create the input box
4132         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);   
4133         Multi_pxo_com_input.set_focus();
4134
4135         // clear all text lines
4136         memset(Multi_pxo_com_bottom_text, 0, 255);
4137         memset(Multi_pxo_com_middle_text, 0, 255);
4138         memset(Multi_pxo_com_top_text, 0, 255);
4139 }
4140
4141 // close down the common dialog
4142 void multi_pxo_com_close()
4143 {
4144         // destroy the UI_WINDOW
4145         Multi_pxo_com_window.destroy();
4146 }
4147
4148 // blit all text lines, top, middle, bottoms
4149 void multi_pxo_com_blit_text()
4150 {
4151         // blit top, middle and bottom text if possible
4152         if(strlen(Multi_pxo_com_top_text) > 0){
4153                 gr_set_color_fast(&Color_bright);
4154                 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);
4155         }
4156         if(strlen(Multi_pxo_com_middle_text) > 0){
4157                 gr_set_color_fast(&Color_bright);
4158                 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);
4159         }
4160         if(strlen(Multi_pxo_com_bottom_text) > 0){
4161                 gr_set_color_fast(&Color_bright);
4162                 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);
4163         }
4164 }
4165
4166 // set the top text, shortening as necessary
4167 void multi_pxo_com_set_top_text(const char *txt)
4168 {       
4169         if((txt != NULL) && strlen(txt)){
4170                 strcpy(Multi_pxo_com_top_text,txt);
4171                 gr_force_fit_string(Multi_pxo_com_top_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4172         }       
4173 }
4174
4175 // set the middle text, shortening as necessary
4176 void multi_pxo_com_set_middle_text(const char *txt)
4177 {
4178         if((txt != NULL) && strlen(txt)){
4179                 strcpy(Multi_pxo_com_middle_text,txt);
4180                 gr_force_fit_string(Multi_pxo_com_middle_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4181         }       
4182 }
4183
4184 // set the bottom text, shortening as necessary
4185 void multi_pxo_com_set_bottom_text(const char *txt)
4186 {
4187         if((txt != NULL) && strlen(txt)){
4188                 strcpy(Multi_pxo_com_bottom_text,txt);
4189                 gr_force_fit_string(Multi_pxo_com_bottom_text, 254, Multi_pxo_com_input_coords[gr_screen.res][2]);
4190         }       
4191 }
4192
4193
4194 // private channel join stuff -----------------------------------------
4195
4196 // initialize the popup
4197 void multi_pxo_priv_init()
4198 {
4199         SDL_assert(Multi_pxo_mode != MULTI_PXO_MODE_PRIVATE);
4200
4201         // initialize the common dialog with the passed max input length
4202         multi_pxo_com_init(MULTI_PXO_PRIV_MAX_TEXT_LEN);
4203         
4204         // initialize the return code
4205         Multi_pxo_priv_return_code = -1;        
4206
4207         // mark us as running
4208         Multi_pxo_mode = MULTI_PXO_MODE_PRIVATE;
4209
4210         // set some text
4211         multi_pxo_com_set_middle_text(XSTR("Type the name of the channel to join/create",961)); 
4212 }
4213
4214 // close down the popup
4215 void multi_pxo_priv_close()
4216 {       
4217         // close down the common dialog
4218         multi_pxo_com_close();
4219
4220         // mark us as not running any more
4221         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
4222 }
4223
4224 // run the popup, 0 if still running, -1 if cancel, 1 if ok 
4225 int multi_pxo_priv_popup()
4226 {
4227         int k;
4228         
4229         // if we're not already running, initialize stuff
4230         if(Multi_pxo_mode != MULTI_PXO_MODE_PRIVATE){
4231                 // intialize
4232                 multi_pxo_priv_init();
4233
4234                 // return "still running"
4235                 return 0;
4236         }
4237
4238         k = Multi_pxo_com_window.process();
4239
4240         // process keypresses
4241         switch(k){
4242         // like hitting the cancel button
4243         case SDLK_ESCAPE:
4244                 Multi_pxo_priv_return_code = 0;
4245                 break;
4246         }
4247
4248         // process button presses
4249         multi_pxo_priv_process_buttons();
4250
4251         // process the inputbox
4252         multi_pxo_priv_process_input();
4253
4254         // blit the background
4255         multi_pxo_blit_all();   
4256
4257         // blit my stuff                
4258         gr_reset_clip();        
4259         gr_set_bitmap(Multi_pxo_com_bitmap);
4260         gr_bitmap(Multi_pxo_com_coords[gr_screen.res][0], Multi_pxo_com_coords[gr_screen.res][1]);
4261         Multi_pxo_com_window.draw();    
4262
4263         // blit all text lines, top, middle, bottoms
4264         multi_pxo_com_blit_text();
4265
4266         gr_flip();
4267
4268         // check the return code
4269         switch(Multi_pxo_priv_return_code){
4270         // still in progress
4271         case -1 :
4272                 return 0;
4273
4274         // user hit cancel
4275         case 0 :
4276                 multi_pxo_priv_close();
4277                 return -1;
4278
4279         // user hit ok
4280         case 1 :                
4281                 multi_pxo_priv_close();
4282                 return 1;
4283         }       
4284
4285         return 0;
4286 }
4287
4288 // process button presses
4289 void multi_pxo_priv_process_buttons()
4290 {
4291         int idx;
4292
4293         // check all buttons
4294         for(idx=0;idx<MULTI_PXO_COM_NUM_BUTTONS;idx++){
4295                 if(Multi_pxo_com_buttons[gr_screen.res][idx].button.pressed()){
4296                         multi_pxo_priv_button_pressed(idx);
4297                         return;
4298                 }
4299         }
4300 }
4301
4302 // handle a button press
4303 void multi_pxo_priv_button_pressed(int n)
4304 {
4305         char priv_chan_name[128];
4306
4307         switch(n){      
4308         case MULTI_PXO_COM_CANCEL:
4309                 Multi_pxo_priv_return_code = 0;
4310                 break;
4311         
4312         case MULTI_PXO_COM_OK:
4313                 Multi_pxo_com_input.get_text(priv_chan_name);
4314                 multi_pxo_strip_space(priv_chan_name,priv_chan_name);
4315
4316                 // if its a 0 length string, interpret as a cancel
4317                 if(strlen(priv_chan_name) <= 0){
4318                         Multi_pxo_priv_return_code = 0;
4319                         return;
4320                 }
4321
4322                 Multi_pxo_priv_return_code = 1;
4323                 break;
4324         }       
4325 }
4326
4327 // process the inputbox
4328 void multi_pxo_priv_process_input()
4329 {
4330         char priv_chan_name[128];
4331         
4332         // see if the user has pressed enter
4333         if(Multi_pxo_com_input.pressed()){
4334                 Multi_pxo_com_input.get_text(priv_chan_name);
4335                 multi_pxo_strip_space(priv_chan_name,priv_chan_name);
4336                 
4337                 // if its a 0 length string, interpret as a cancel
4338                 if(strlen(priv_chan_name) <= 0){
4339                         Multi_pxo_priv_return_code = 0;
4340                         return;
4341                 }
4342
4343                 // otherwise interpret as "accept"
4344                 Multi_pxo_priv_return_code = 1;
4345
4346                 // add in the "+" which indicates a private room
4347                 strcpy(Multi_pxo_priv_chan,"+");
4348                 strcat(Multi_pxo_priv_chan,priv_chan_name);
4349         }
4350 }
4351
4352 // find player stuff -----------------------------------------
4353
4354 char name_lookup[255];
4355
4356 // initialize the popup
4357 void multi_pxo_find_init()
4358 {
4359         SDL_assert(Multi_pxo_mode != MULTI_PXO_MODE_FIND);
4360
4361         // initialize the common dialog with the passed max input length
4362         multi_pxo_com_init(MAX_PLAYER_NAME_LEN);        
4363
4364         // return code, set to something other than -1 if we're supposed to return
4365         Multi_pxo_find_return_code = -1;
4366
4367         // mark us as running
4368         Multi_pxo_mode = MULTI_PXO_MODE_FIND;   
4369
4370         // not searching yet
4371         Multi_pxo_searching = 0; 
4372
4373         // set the top text
4374         multi_pxo_com_set_top_text(XSTR("Enter user to be found",962)); 
4375
4376         // 0 length
4377         strcpy(Multi_pxo_find_channel,"");
4378
4379         // 0 length
4380         strcpy(name_lookup,"");
4381 }
4382
4383 // close down the popup
4384 void multi_pxo_find_close()
4385 {
4386         // close down the common dialog
4387         multi_pxo_com_close();
4388
4389         // mark us as not running any more
4390         Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
4391 }
4392
4393 // run the popup, 0 if still running, -1 if cancel, 1 if ok 
4394 int multi_pxo_find_popup()
4395 {
4396         int k;
4397         
4398         // if we're not already running, initialize stuff
4399         if(Multi_pxo_mode != MULTI_PXO_MODE_FIND){
4400                 // intialize
4401                 multi_pxo_find_init();
4402
4403                 // return "still running"
4404                 return 0;
4405         }
4406
4407         k = Multi_pxo_com_window.process();
4408
4409         // process keypresses
4410         switch(k){
4411         // like hitting the cancel button
4412         case SDLK_ESCAPE:
4413                 Multi_pxo_find_return_code = 0;
4414                 break;
4415         }
4416
4417         // process button presses
4418         multi_pxo_find_process_buttons();
4419
4420         // process the inputbox
4421         multi_pxo_find_process_input();
4422
4423         // process search mode if applicable
4424         multi_pxo_find_search_process();
4425
4426         // blit the background
4427         multi_pxo_blit_all();   
4428
4429         // blit my stuff                
4430         gr_reset_clip();        
4431         gr_set_bitmap(Multi_pxo_com_bitmap);
4432         gr_bitmap(Multi_pxo_com_coords[gr_screen.res][0], Multi_pxo_com_coords[gr_screen.res][1]);
4433         Multi_pxo_com_window.draw();    
4434
4435         // blit any text lines
4436         multi_pxo_com_blit_text();
4437         
4438         gr_flip();
4439
4440         // check the return code
4441         switch(Multi_pxo_find_return_code){
4442         // still in progress
4443         case -1 :
4444                 return 0;
4445
4446         // user hit cancel
4447         case 0 :
4448                 // close the popup down
4449                 multi_pxo_find_close();
4450                 return -1;
4451
4452         // user hit ok
4453         case 1 :                
4454                 // close the popup down
4455                 multi_pxo_find_close();
4456
4457                 // if we have a channel, join it now if possible
4458                 if(strlen(Multi_pxo_find_channel) > 0){
4459                         pxo_channel *lookup;
4460                         lookup = multi_pxo_find_channel(Multi_pxo_find_channel,Multi_pxo_channels);
4461                         
4462                         // if we couldn't find it, don't join
4463                         if(lookup != NULL){                             
4464                                 multi_pxo_join_channel(lookup);
4465                         }
4466                 }
4467                 return 1;
4468         }       
4469
4470         return 0;
4471 }
4472
4473 // process button presses
4474 void multi_pxo_find_process_buttons()
4475 {
4476         int idx;
4477
4478         // check all buttons
4479         for(idx=0;idx<MULTI_PXO_COM_NUM_BUTTONS;idx++){
4480                 if(Multi_pxo_com_buttons[gr_screen.res][idx].button.pressed()){
4481                         multi_pxo_find_button_pressed(idx);
4482                         return;
4483                 }
4484         }
4485 }
4486
4487 // handle a button press
4488 void multi_pxo_find_button_pressed(int n)
4489 {
4490         switch(n){      
4491         case MULTI_PXO_COM_CANCEL:
4492                 Multi_pxo_find_return_code = 0;
4493                 break;
4494         
4495         case MULTI_PXO_COM_OK:
4496                 Multi_pxo_find_return_code = 1;
4497                 break;
4498         }       
4499 }
4500
4501 // process the inputbox
4502 void multi_pxo_find_process_input()
4503 {               
4504         // see if the user has pressed enter
4505         if(Multi_pxo_com_input.pressed()){
4506                 // if we're not already in search mode
4507                 if(!Multi_pxo_searching){
4508                         // clear all text
4509                         memset(Multi_pxo_com_middle_text,0,255);
4510                         memset(Multi_pxo_com_bottom_text,0,255);
4511
4512                         Multi_pxo_com_input.get_text(name_lookup);
4513                         multi_pxo_strip_space(name_lookup,name_lookup);
4514
4515                         // never search with a zero length string
4516                         if(strlen(name_lookup) > 0){
4517                                 char search_text[512];
4518
4519                                 // put us in search mode
4520                                 Multi_pxo_searching = 1;
4521
4522                                 // look for the guy
4523                                 GetChannelByUser(name_lookup);                  
4524
4525                                 // set the top text
4526                                 memset(search_text,0,512);
4527                                 sprintf(search_text,XSTR("Searching for %s",963),name_lookup);
4528                                 multi_pxo_com_set_top_text(search_text);
4529                         }
4530                         // clear everything
4531                         else {
4532                                 memset(Multi_pxo_com_top_text,0,255);
4533                         }
4534                 }
4535         }
4536 }
4537
4538 // process search mode if applicable
4539 void multi_pxo_find_search_process()
4540 {
4541         char *channel;
4542         
4543         // if we're not searching for anything, return
4544         if(!Multi_pxo_searching){
4545                 return;
4546         }
4547
4548         // otherwise check to see if we've found him
4549         channel = GetChannelByUser(NULL);
4550         
4551         // if we've got a result, let the user know
4552         if(channel){
4553                 // if he couldn't be found
4554                 if(channel == (char *)-1){
4555                         multi_pxo_com_set_middle_text(XSTR("User not found",964));                                                                      
4556                         strcpy(Multi_pxo_find_channel,"");
4557                 } else {        
4558                         if(channel[0] == '*'){
4559                                 multi_pxo_com_set_middle_text(XSTR("Player is logged in but is not on a channel",965));                         
4560                                 strcpy(Multi_pxo_find_channel,"");
4561                         } else {
4562                                 char p_text[512];
4563                                 memset(p_text,0,512);
4564
4565                                 // if this guy is on a public channel, display which one
4566                                 if(channel[0] == '#'){                  
4567                                         sprintf(p_text,XSTR("Found %s on :",966),name_lookup);
4568
4569                                         // display the results                                                          
4570                                         multi_pxo_com_set_middle_text(p_text);                                                          
4571                                         multi_pxo_com_set_bottom_text(channel+1);
4572
4573                                         // mark down the channel name so we know where to find him
4574                                         strcpy(Multi_pxo_find_channel,channel);         
4575                                         // strip out trailing whitespace
4576                                         if(Multi_pxo_find_channel[strlen(Multi_pxo_find_channel) - 1] == ' '){
4577                                                 Multi_pxo_find_channel[strlen(Multi_pxo_find_channel) - 1] = '\0';
4578                                         }                               
4579                                 }
4580                                 // if this is a private channel
4581                                 else if(channel[0] == '+'){
4582                                         sprintf(p_text,XSTR("Found %s on a private channel",967),name_lookup);
4583                                         multi_pxo_com_set_middle_text(p_text);
4584
4585                                         strcpy(Multi_pxo_find_channel,"");
4586                                 }                                                               
4587                         }
4588                 }
4589
4590                 // unset search mode
4591                 Multi_pxo_searching = 0;
4592
4593                 // clear the inputbox
4594                 Multi_pxo_com_input.set_text("");
4595         }
4596 }
4597
4598
4599 // player info stuff -----------------------------------------
4600
4601 // popup conditional functions, returns 10 on successful get of stats
4602 int multi_pxo_pinfo_cond()
4603 {
4604         char *ret_string;
4605         char temp_string[255];
4606         char *tok;
4607
4608         // process common stuff
4609         multi_pxo_process_common();
4610
4611         // run the networking functions for the PXO API
4612         multi_pxo_api_process();
4613
4614         // process depending on what mode we're in
4615         switch(Multi_pxo_retrieve_mode){        
4616         // getting his player tracker id
4617         case 0:         
4618                 // if the thing is non-null, do something               
4619                 ret_string = GetTrackerIdByUser(Multi_pxo_retrieve_name);
4620                 if(ret_string != NULL){
4621                         // user not-online/not found
4622                         if(ret_string == (char *)-1){
4623                                 return 1;
4624                         } 
4625
4626                         // user not a tracker pilot
4627                         if(!SDL_strcasecmp(ret_string,"-1")){
4628                                 return 1;
4629                         }
4630
4631                         // otherwise parse into his id and callsign
4632                         strcpy(temp_string,ret_string);
4633                         tok = strtok(temp_string," ");
4634                         
4635                         // get tracker id
4636                         if(tok != NULL){
4637                                 strcpy(Multi_pxo_retrieve_id,tok);
4638
4639                                 // get the callsign
4640                                 tok = strtok(NULL,"");
4641                                 if(tok != NULL){
4642                                         strcpy(Multi_pxo_retrieve_name,tok);
4643                                 }
4644                                 // failure
4645                                 else {
4646                                         return 1;
4647                                 }
4648                         }
4649                         // failure of some kind or another
4650                         else {
4651                                 return 1;
4652                         }                       
4653
4654                         Multi_pxo_retrieve_mode = 1;
4655                         return 0;                       
4656                 }
4657                 break;
4658
4659         // initial call to get his stats
4660         case 1:         
4661                 // change the popup text
4662                 popup_change_text(XSTR("Getting player stats",968));
4663
4664                 // fill in the data
4665                 memset(&Multi_pxo_pinfo, 0, sizeof(vmt_freespace2_struct));
4666                 strcpy(Multi_pxo_pinfo.pilot_name, Multi_pxo_retrieve_name);
4667                 strncpy(Multi_pxo_pinfo.tracker_id, Multi_pxo_retrieve_id, TRACKER_ID_LEN);
4668
4669                 // make the initial call to the API
4670                 GetFSPilotData((vmt_freespace2_struct*)0xffffffff,NULL,NULL,0);
4671                 if(GetFSPilotData(&Multi_pxo_pinfo,Multi_pxo_retrieve_name,Multi_pxo_retrieve_id,0) != 0){
4672                         return 2;
4673                 }
4674                 // if the call went through, set the mode to 2
4675                 else {
4676                         Multi_pxo_retrieve_mode = 2;
4677                 }
4678                 break;
4679         
4680         // busy retrieving his stats
4681         case 2:
4682                 switch(GetFSPilotData(NULL,NULL,NULL,0)){
4683                 // timeout, fail, cancel
4684                 case -1:
4685                 case 3:
4686                 case 2:
4687                         return 2;                       
4688
4689                 // got the data
4690                 case 1:
4691                         return 10;
4692
4693                 // still busy
4694                 case 0:
4695                         break;
4696                 }
4697
4698                 break;          
4699         }
4700
4701         // return not done yet
4702         return 0;
4703 }
4704
4705 // return 1 if Multi_pxo_pinfo was successfully filled in, 0 otherwise
4706 int multi_pxo_pinfo_get(char *name)
4707 {
4708         // run the popup        
4709         Multi_pxo_retrieve_mode = 0;
4710         strcpy(Multi_pxo_retrieve_name,name);   
4711         switch(popup_till_condition(multi_pxo_pinfo_cond,XSTR("&Cancel", 779),XSTR("Retrieving player tracker id",969))){
4712         // success
4713         case 10 :
4714                 return 1;               
4715
4716         // failed to get his tracker id
4717         case 1 :
4718                 return 0;
4719
4720         // failed to get his stats
4721         case 2 :
4722                 return 0;       
4723         }
4724                         
4725         // we didn't get the stats
4726         return 0;
4727 }
4728
4729 // fire up the stats view popup
4730 void multi_pxo_pinfo_show()
4731 {
4732         // initialize the popup
4733         multi_pxo_pinfo_init();
4734         
4735         // run the popup
4736         do {
4737                 game_set_frametime(GS_STATE_PXO);
4738         } while(!multi_pxo_pinfo_do());
4739
4740         // close down the popup
4741         multi_pxo_pinfo_close();
4742 }
4743
4744 // build the stats labels values
4745 void multi_pxo_pinfo_build_vals()
4746 {
4747         vmt_freespace2_struct *fs = &Multi_pxo_pinfo;   
4748                         
4749         // pilot name
4750         memset(Multi_pxo_pinfo_vals[0],0,50);
4751         strcpy(Multi_pxo_pinfo_vals[0],fs->pilot_name);
4752         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]));
4753
4754         // rank
4755         memset(Multi_pxo_pinfo_vals[1],0,50);   
4756         multi_sg_rank_build_name(Ranks[fs->rank].name,Multi_pxo_pinfo_vals[1]); 
4757         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]));
4758
4759         // kills
4760         memset(Multi_pxo_pinfo_vals[2],0,50);
4761         sprintf(Multi_pxo_pinfo_vals[2],"%d",fs->kill_count);
4762
4763         // assists
4764         memset(Multi_pxo_pinfo_vals[3],0,50);
4765         sprintf(Multi_pxo_pinfo_vals[3],"%d",fs->assists);
4766
4767         // friendly kills
4768         memset(Multi_pxo_pinfo_vals[4],0,50);
4769         sprintf(Multi_pxo_pinfo_vals[4],"%d",fs->kill_count - fs->kill_count_ok);
4770
4771         // missions flown
4772         memset(Multi_pxo_pinfo_vals[5],0,50);
4773         sprintf(Multi_pxo_pinfo_vals[5],"%d",(int)fs->missions_flown);  
4774
4775         // flight time  
4776         memset(Multi_pxo_pinfo_vals[6],0,50);
4777         game_format_time(fl2f((float)fs->flight_time),Multi_pxo_pinfo_vals[6]); 
4778
4779         // last flown
4780         memset(Multi_pxo_pinfo_vals[7],0,50);
4781         if(fs->last_flown == 0){                
4782                 strcpy(Multi_pxo_pinfo_vals[7],XSTR("No missions flown",970));
4783         } else {
4784                 tm *tmr = gmtime((time_t*)&fs->last_flown);
4785                 if(tmr != NULL){
4786                         strftime(Multi_pxo_pinfo_vals[7],30,"%m/%d/%y %H:%M",tmr);      
4787                 } else {
4788                         strcpy(Multi_pxo_pinfo_vals[7], "");
4789                 }
4790         }               
4791
4792         // primary shots fired
4793         memset(Multi_pxo_pinfo_vals[8],0,50);
4794         sprintf(Multi_pxo_pinfo_vals[8],"%d",(int)fs->p_shots_fired);
4795
4796         // primary shots hit
4797         memset(Multi_pxo_pinfo_vals[9],0,50);
4798         sprintf(Multi_pxo_pinfo_vals[9],"%d",(int)fs->p_shots_hit);
4799
4800         // primary hit pct
4801         memset(Multi_pxo_pinfo_vals[10],0,50);
4802         if(fs->p_shots_fired > 0){              
4803                 sprintf(Multi_pxo_pinfo_vals[10],"%d%%",(int)((float)fs->p_shots_hit / (float)fs->p_shots_fired * 100.0f));
4804         } else {                
4805                 strcpy(Multi_pxo_pinfo_vals[10],"0%");
4806         }
4807
4808         // secondary shots fired
4809         memset(Multi_pxo_pinfo_vals[11],0,50);
4810         sprintf(Multi_pxo_pinfo_vals[11],"%d",(int)fs->s_shots_fired);
4811
4812         // secondary shots hit
4813         memset(Multi_pxo_pinfo_vals[12],0,50);
4814         sprintf(Multi_pxo_pinfo_vals[12],"%d",(int)fs->s_shots_hit);
4815
4816         // secondary hit pct
4817         memset(Multi_pxo_pinfo_vals[13],0,50);
4818         if(fs->s_shots_fired > 0){              
4819                 sprintf(Multi_pxo_pinfo_vals[13],"%d%%",(int)((float)fs->s_shots_hit / (float)fs->s_shots_fired * 100.0f));
4820         } else {                
4821                 strcpy(Multi_pxo_pinfo_vals[13],"0%");
4822         }
4823
4824         // primary friendly hits
4825         memset(Multi_pxo_pinfo_vals[14],0,50);
4826         sprintf(Multi_pxo_pinfo_vals[14],"%d",fs->p_bonehead_hits);
4827
4828         // primary friendly hit %
4829         memset(Multi_pxo_pinfo_vals[15],0,50);
4830         if(fs->p_shots_hit > 0){                
4831            sprintf(Multi_pxo_pinfo_vals[15],"%d%%",(int)((float)100.0f*((float)fs->p_bonehead_hits/(float)fs->p_shots_fired)));
4832         } else {                
4833                 strcpy(Multi_pxo_pinfo_vals[15],"0%");
4834         }
4835
4836         // secondary friendly hits
4837         memset(Multi_pxo_pinfo_vals[16],0,50);
4838         sprintf(Multi_pxo_pinfo_vals[16],"%d",fs->s_bonehead_hits);
4839
4840         // secondary friendly hit %
4841         memset(Multi_pxo_pinfo_vals[17],0,50);
4842         if(fs->s_shots_hit > 0){
4843            sprintf(Multi_pxo_pinfo_vals[17],"%d%%",(int)((float)100.0f*((float)fs->s_bonehead_hits/(float)fs->s_shots_fired)));
4844         } else {                
4845                 strcpy(Multi_pxo_pinfo_vals[17],"0%");
4846         }
4847 }
4848
4849 // initialize the popup
4850 void multi_pxo_pinfo_init()
4851 {
4852         int idx;
4853         
4854         // create the interface window
4855         Multi_pxo_pinfo_window.create(0,0,gr_screen.max_w,gr_screen.max_h,0);
4856         Multi_pxo_pinfo_window.set_mask_bmap(Multi_pxo_pinfo_mask_fname[gr_screen.res]);        
4857         
4858         Multi_pxo_pinfo_bitmap = bm_load(Multi_pxo_pinfo_fname[gr_screen.res]);
4859         SDL_assert(Multi_pxo_pinfo_bitmap != -1);
4860
4861         // create the interface buttons
4862         for(idx=0; idx<MULTI_PXO_PINFO_NUM_BUTTONS; idx++){
4863                 // create the object
4864                 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);
4865
4866                 // set the sound to play when highlighted
4867                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
4868
4869                 // set the ani for the button
4870                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_pinfo_buttons[gr_screen.res][idx].filename);
4871
4872                 // set the hotspot
4873                 Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_pinfo_buttons[gr_screen.res][idx].hotspot);
4874         }                               
4875
4876         // add xstrs
4877         for(idx=0; idx<MULTI_PXO_PINFO_NUM_TEXT; idx++){
4878                 Multi_pxo_pinfo_window.add_XSTR(&Multi_pxo_pinfo_text[gr_screen.res][idx]);
4879         }
4880
4881         // set up the stats labels
4882         Multi_pxo_pinfo_stats_labels[0] = strdup(XSTR("Name", 1532));
4883         Multi_pxo_pinfo_stats_labels[1] = strdup(XSTR("Rank", 1533));
4884         Multi_pxo_pinfo_stats_labels[2] = strdup(XSTR("Kills", 1534));
4885         Multi_pxo_pinfo_stats_labels[3] = strdup(XSTR("Assists", 1535));
4886         Multi_pxo_pinfo_stats_labels[4] = strdup(XSTR("Friendly kills", 1536));
4887         Multi_pxo_pinfo_stats_labels[5] = strdup(XSTR("Missions flown", 1537));
4888         Multi_pxo_pinfo_stats_labels[6] = strdup(XSTR("Flight time", 1538));
4889         Multi_pxo_pinfo_stats_labels[7] = strdup(XSTR("Last flown", 1539));
4890         Multi_pxo_pinfo_stats_labels[8] = strdup(XSTR("Primary shots fired", 1540));
4891         Multi_pxo_pinfo_stats_labels[9] = strdup(XSTR("Primary shots hit", 1541));
4892         Multi_pxo_pinfo_stats_labels[10] = strdup(XSTR("Primary hit %", 1542));
4893         Multi_pxo_pinfo_stats_labels[11] = strdup(XSTR("Secondary shots fired", 1543));
4894         Multi_pxo_pinfo_stats_labels[12] = strdup(XSTR("Secondary shots hit", 1544));
4895         Multi_pxo_pinfo_stats_labels[13] = strdup(XSTR("Secondary hit %", 1545));
4896         Multi_pxo_pinfo_stats_labels[14] = strdup(XSTR("Primary friendly hits", 1546));
4897         Multi_pxo_pinfo_stats_labels[15] = strdup(XSTR("Primary friendly hit %", 1547));
4898         Multi_pxo_pinfo_stats_labels[16] = strdup(XSTR("Secondary friendly hits", 1548));
4899         Multi_pxo_pinfo_stats_labels[17] = strdup(XSTR("Secondary friendly hit %", 1549));
4900
4901         // build the stats labels values
4902         multi_pxo_pinfo_build_vals();
4903 }
4904
4905 // do frame
4906 int multi_pxo_pinfo_do()
4907 {
4908         int k = Multi_pxo_pinfo_window.process();
4909
4910         // process common stuff
4911         multi_pxo_process_common();
4912
4913         // run the networking functions for the PXO API
4914         multi_pxo_api_process();
4915
4916         // check to see if he pressed escp
4917         if(k == SDLK_ESCAPE){
4918                 return 1;
4919         }
4920
4921         // if he pressed the ok button
4922         if(Multi_pxo_pinfo_buttons[gr_screen.res][MULTI_PXO_PINFO_OK].button.pressed()){
4923                 return 1;
4924         }
4925
4926         // if he pressed the medals buttons, run the medals screen
4927         if(Multi_pxo_pinfo_buttons[gr_screen.res][MULTI_PXO_PINFO_MEDALS].button.pressed()){
4928 #ifdef FS2_DEMO
4929         game_feature_not_in_demo_popup();
4930 #else
4931                 multi_pxo_run_medals();
4932 #endif
4933         }
4934         
4935         // draw stuff
4936
4937         // blit everything on the "normal" screen
4938         multi_pxo_blit_all();
4939
4940         // blit our own stuff
4941         gr_reset_clip();        
4942         gr_set_bitmap(Multi_pxo_pinfo_bitmap);
4943         gr_bitmap(0, 0);
4944         Multi_pxo_pinfo_window.draw();  
4945
4946         // blit the stats themselves
4947         multi_pxo_pinfo_blit();
4948
4949         // flip the page
4950         gr_flip();
4951
4952         // not done yet
4953         return 0;
4954 }
4955
4956 // close
4957 void multi_pxo_pinfo_close()
4958 {
4959         int i;
4960
4961         // destroy the UI_WINDOW
4962         Multi_pxo_pinfo_window.destroy();
4963
4964         // unload the bitmap
4965         if(Multi_pxo_pinfo_bitmap != -1){
4966                 bm_unload(Multi_pxo_pinfo_bitmap);
4967         }
4968
4969         // free the stats labels strings
4970         for (i=0; i<MULTI_PXO_PINFO_NUM_LABELS; i++) {
4971                 free(Multi_pxo_pinfo_stats_labels[i]);
4972         }
4973 }
4974
4975 // blit all the stats on this screen
4976 void multi_pxo_pinfo_blit()
4977 {
4978         int idx;
4979         int y_start;
4980         
4981         // blit all the labels  
4982         y_start = Multi_pxo_pinfo_coords[gr_screen.res][1];
4983         for(idx=0; idx<MULTI_PXO_PINFO_NUM_LABELS; idx++){
4984                 // blit the label
4985                 gr_set_color_fast(&Color_bright);
4986                 gr_string(Multi_pxo_pinfo_coords[gr_screen.res][0], y_start, Multi_pxo_pinfo_stats_labels[idx]);
4987
4988                 // blit the label's value
4989                 gr_set_color_fast(&Color_normal);
4990                 gr_string(Multi_pxo_pinfo_val_x[gr_screen.res], y_start, Multi_pxo_pinfo_vals[idx]);
4991
4992                 // spacing
4993                 y_start += Multi_pxo_pinfo_stats_spacing[idx];
4994         }
4995 }
4996
4997 // run the medals screen
4998 void multi_pxo_run_medals()
4999 {       
5000         int ret_code;
5001         
5002         // process common stuff
5003         multi_pxo_process_common();
5004
5005         // run the networking functions for the PXO API
5006         multi_pxo_api_process();
5007
5008         // initialize the freespace data and the player struct  
5009         multi_stats_tracker_to_fs(&Multi_pxo_pinfo, &Multi_pxo_pinfo_player.stats);
5010         strcpy(Multi_pxo_pinfo_player.callsign, Multi_pxo_pinfo.pilot_name);
5011         
5012         // initialize the medals screen
5013         medal_main_init(&Multi_pxo_pinfo_player, MM_POPUP);
5014
5015         // run the medals screen until it says that it should be closed
5016         do {
5017                 // set frametime and run common functions
5018                 game_set_frametime(-1);
5019                 game_do_state_common(gameseq_get_state());
5020
5021                 // run the medals screen
5022                 ret_code = medal_main_do();             
5023         } while(ret_code);
5024
5025         // close the medals screen down
5026         medal_main_close();
5027         
5028         // reset the palette
5029         multi_pxo_load_palette();
5030 }
5031
5032
5033 // notify stuff stuff -----------------------------------------
5034
5035 // add a notification string
5036 void multi_pxo_notify_add(const char *txt)
5037 {
5038         // copy the text
5039         strcpy(Multi_pxo_notify_text, txt);
5040
5041         // set the timestamp
5042         Multi_pxo_notify_stamp = timestamp(MULTI_PXO_NOTIFY_TIME);
5043 }
5044
5045 // blit and process the notification string
5046 void multi_pxo_notify_blit()
5047 {
5048         int w;
5049
5050         // if the timestamp is -1, do nothing
5051         if(Multi_pxo_notify_stamp == -1){
5052                 return;
5053         }
5054
5055         // if it has expired, do nothing
5056         if(timestamp_elapsed(Multi_pxo_notify_stamp)){
5057                 Multi_pxo_notify_stamp = -1;
5058         }
5059
5060         // otherwise blit the text
5061         gr_set_color_fast(&Color_bright);
5062         gr_get_string_size(&w,NULL,Multi_pxo_notify_text);
5063         gr_string((gr_screen.max_w - w)/2,MULTI_PXO_NOTIFY_Y,Multi_pxo_notify_text);
5064 }
5065
5066
5067 // initialize the PXO help screen
5068 void multi_pxo_help_init()
5069 {
5070         int idx;
5071         
5072         // load the background bitmap
5073         Multi_pxo_help_bitmap = bm_load(Multi_pxo_help_fname[gr_screen.res]);
5074         if(Multi_pxo_help_bitmap < 0){
5075                 // we failed to load the bitmap - this is very bad
5076                 Int3();
5077         }
5078                 // create the interface window
5079         Multi_pxo_help_window.create(0,0,gr_screen.max_w,gr_screen.max_h,0);
5080         Multi_pxo_help_window.set_mask_bmap(Multi_pxo_help_mask_fname[gr_screen.res]);
5081
5082                 // create the interface buttons
5083         for(idx=0; idx<MULTI_PXO_HELP_NUM_BUTTONS; idx++){
5084                 // create the object
5085                 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);
5086
5087                 // set the sound to play when highlighted
5088                 Multi_pxo_help_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
5089
5090                 // set the ani for the button
5091                 Multi_pxo_help_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_help_buttons[gr_screen.res][idx].filename);
5092
5093                 // set the hotspot
5094                 Multi_pxo_help_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_help_buttons[gr_screen.res][idx].hotspot);
5095         }       
5096         
5097         // add xstrs
5098         for(idx=0; idx<MULTI_PXO_HELP_NUM_TEXT; idx++){
5099                 Multi_pxo_help_window.add_XSTR(&Multi_pxo_help_text[gr_screen.res][idx]);
5100         }
5101
5102         // if we haven't already loaded in the text, do so
5103         // if(!Multi_pxo_help_loaded){
5104                 multi_pxo_help_load();
5105         // }
5106
5107         // set the current page to 0
5108         Multi_pxo_help_cur = 0;
5109 }
5110
5111 // do frame for PXO help
5112 void multi_pxo_help_do()
5113 {
5114         // run api stuff        
5115         if(Multi_pxo_connected){
5116                 multi_pxo_api_process();
5117         }
5118
5119         // process common stuff
5120         multi_pxo_process_common();
5121
5122         int k = Multi_pxo_help_window.process();
5123
5124         // process any keypresses
5125         switch(k){
5126         case SDLK_ESCAPE:
5127                 gamesnd_play_iface(SND_USER_SELECT);
5128                 gameseq_post_event(GS_EVENT_PXO);
5129                 break;
5130         }               
5131
5132         // process button presses
5133         multi_pxo_help_process_buttons();
5134
5135         // draw the background, etc
5136         gr_reset_clip();
5137         GR_MAYBE_CLEAR_RES(Multi_pxo_help_bitmap);
5138         if(Multi_pxo_help_bitmap != -1){
5139                 gr_set_bitmap(Multi_pxo_help_bitmap);
5140                 gr_bitmap(0,0);
5141         }
5142         Multi_pxo_help_window.draw();
5143
5144         // blit the current page
5145         multi_pxo_help_blit_page();
5146
5147         // page flip
5148         gr_flip();
5149 }
5150
5151 // close the pxo screen
5152 void multi_pxo_help_close()
5153 {
5154         int idx, idx2;
5155
5156         // unload any bitmaps
5157         bm_unload(Multi_pxo_help_bitmap);               
5158         
5159         // destroy the UI_WINDOW
5160         Multi_pxo_help_window.destroy();
5161
5162         // free all pages
5163         for(idx=0; idx<Multi_pxo_help_num_pages; idx++){
5164                 for(idx2=0; idx2<Multi_pxo_help_pages[idx].num_lines; idx2++){
5165                         // maybe free
5166                         if(Multi_pxo_help_pages[idx].text[idx2] != NULL){
5167                                 free(Multi_pxo_help_pages[idx].text[idx2]);
5168                                 Multi_pxo_help_pages[idx].text[idx2] = NULL;
5169                         }
5170                 }
5171         }
5172 }
5173
5174 // load the help file up
5175 void multi_pxo_help_load()
5176 {
5177         CFILE *in;      
5178         help_page *cp;  
5179         
5180         // if its already loaded, do nothing
5181         // if(Multi_pxo_help_loaded){
5182                 // return;
5183         // }
5184
5185         // read in the text file
5186         in = NULL;
5187         in = cfopen(MULTI_PXO_HELP_FILE,"rt",CFILE_NORMAL,CF_TYPE_DATA);                        
5188         SDL_assert(in != NULL);
5189         if(in == NULL){
5190                 return;
5191         }
5192
5193         Multi_pxo_help_num_pages = 0;
5194
5195         // blast all the help pages clear
5196         memset(Multi_pxo_help_pages, 0, sizeof(help_page) * MULTI_PXO_MAX_PAGES);       
5197         Multi_pxo_help_num_pages = 0;
5198         cp = &Multi_pxo_help_pages[0];
5199
5200         while(!cfeof(in)){
5201                 // malloc the line
5202                 cp->text[cp->num_lines] = (char*)malloc(Multi_pxo_chars_per_line[gr_screen.res]);
5203                 if(cp->text[cp->num_lines] == NULL){
5204                         break;
5205                 }
5206                 
5207                 // read in the next line                
5208                 cfgets(cp->text[cp->num_lines++], Multi_pxo_chars_per_line[gr_screen.res], in);
5209
5210                 // skip to the next page if necessary
5211                 if(cp->num_lines == Multi_pxo_lines_pp[gr_screen.res]){                 
5212                         Multi_pxo_help_num_pages++;
5213                         SDL_assert(Multi_pxo_help_num_pages < MULTI_PXO_MAX_PAGES);
5214                         if(Multi_pxo_help_num_pages >= MULTI_PXO_MAX_PAGES){
5215                                 Multi_pxo_help_num_pages--;
5216                                 break;
5217                         }
5218                         cp = &Multi_pxo_help_pages[Multi_pxo_help_num_pages];
5219                 }
5220         }
5221
5222         // close the file
5223         cfclose(in);
5224
5225         // mark the help as having been loaded
5226         // Multi_pxo_help_loaded = 1;
5227 }
5228
5229 // blit the current page
5230 void multi_pxo_help_blit_page()
5231 {
5232         int idx;
5233         int start_pos;
5234         int y_start;
5235         help_page *cp = &Multi_pxo_help_pages[Multi_pxo_help_cur];
5236         
5237         // blit each line
5238         y_start = Multi_pxo_help_coords[gr_screen.res][1];
5239         for(idx=0;idx<cp->num_lines;idx++){
5240                 // if the first symbol is "@", highlight the line
5241                 if(cp->text[idx][0] == '@'){
5242                         gr_set_color_fast(&Color_bright);
5243                         start_pos = 1;
5244                 } else {
5245                         gr_set_color_fast(&Color_normal);
5246                         start_pos = 0;
5247                 }
5248
5249                 // blit the line
5250                 gr_string(Multi_pxo_help_coords[gr_screen.res][0], y_start, cp->text[idx] + start_pos);
5251
5252                 // increment the y location
5253                 y_start += 10;
5254         }
5255 }
5256
5257 // process button presses
5258 void multi_pxo_help_process_buttons()
5259 {
5260         int idx;
5261
5262         // process all buttons
5263         for(idx=0;idx<MULTI_PXO_HELP_NUM_BUTTONS;idx++){
5264                 if(Multi_pxo_help_buttons[gr_screen.res][idx].button.pressed()){
5265                         multi_pxo_help_button_pressed(idx);
5266                         return;
5267                 }
5268         }
5269 }
5270
5271 // button pressed
5272 void multi_pxo_help_button_pressed(int n)
5273 {       
5274         switch(n){
5275         case MULTI_PXO_HELP_PREV:
5276                 // if we're already at page 0, do nothing
5277                 if(Multi_pxo_help_cur == 0){
5278                         gamesnd_play_iface(SND_GENERAL_FAIL);                   
5279                 } else {
5280                         Multi_pxo_help_cur--;
5281                         gamesnd_play_iface(SND_USER_SELECT);
5282                 }
5283                 break;
5284
5285         case MULTI_PXO_HELP_NEXT:
5286                 // if we're already at max pages, do nothing
5287                 if(Multi_pxo_help_cur == Multi_pxo_help_num_pages){
5288                         gamesnd_play_iface(SND_GENERAL_FAIL);
5289                 } else {
5290                         Multi_pxo_help_cur++;
5291                         gamesnd_play_iface(SND_USER_SELECT);
5292                 }
5293                 break;
5294
5295         case MULTI_PXO_HELP_CONTINUE:
5296                 gamesnd_play_iface(SND_USER_SELECT);
5297                 gameseq_post_event(GS_EVENT_PXO);
5298                 break;
5299         }
5300 }
5301
5302 // http banner stuff ---------------------------------------------
5303
5304 // init
5305 void multi_pxo_ban_init()
5306 {
5307         // zero the active banner bitmap
5308         Multi_pxo_banner.ban_bitmap = -1;       
5309
5310         // are we doing banners at all?
5311         if(os_config_read_uint(NULL, "PXOBanners", 1)){
5312                 // if we're already in idle mode, we're done downloading for this instance of freespace. pick a random image we already have
5313                 if(Multi_pxo_ban_mode == PXO_BAN_MODE_IDLE){
5314                         Multi_pxo_ban_mode = PXO_BAN_MODE_CHOOSE_RANDOM;                
5315                         return;
5316                 }
5317                 
5318                 // set ourselves to startup mode        
5319                 Multi_pxo_ban_mode = PXO_BAN_MODE_LIST_STARTUP;
5320                 Multi_pxo_ban_get = NULL;
5321         } else {
5322                 // set ourselves to idle mode
5323                 Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5324                 Multi_pxo_ban_get = NULL;
5325         }
5326
5327         // zero the active banner bitmap
5328         Multi_pxo_banner.ban_bitmap = -1;       
5329         strcpy(Multi_pxo_banner.ban_file, "");
5330         strcpy(Multi_pxo_banner.ban_file_url, "");
5331         strcpy(Multi_pxo_banner.ban_url, "");   
5332 }
5333
5334 // process http download details
5335 void multi_pxo_ban_process()
5336 {
5337         char url_string[512] = "";
5338         char local_file[512] = "";
5339
5340         // process stuff
5341         switch(Multi_pxo_ban_mode){
5342         // start downloading list
5343         case PXO_BAN_MODE_LIST_STARTUP:         
5344                 // remote file
5345                 sprintf(url_string, "%s/%s", Multi_options_g.pxo_banner_url, PXO_BANNERS_CONFIG_FILE);
5346
5347                 // local file
5348                 cf_create_default_path_string(local_file, CF_TYPE_MULTI_CACHE, PXO_BANNERS_CONFIG_FILE);
5349                 
5350                 // try creating the file get object
5351                 Multi_pxo_ban_get = new InetGetFile(url_string, local_file);
5352
5353                 // bad
5354                 if(Multi_pxo_ban_get == NULL){                  
5355                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5356                 }
5357
5358                 // go to the downloading list mode
5359                 Multi_pxo_ban_mode = PXO_BAN_MODE_LIST;
5360                 break;
5361
5362         // downloading list
5363         case PXO_BAN_MODE_LIST:
5364                 // error
5365                 if(Multi_pxo_ban_get->IsFileError()){                   
5366                         delete Multi_pxo_ban_get;
5367                         Multi_pxo_ban_get = NULL;
5368                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5369                         break;
5370                 } 
5371
5372                 // connecting, receiving
5373                 if(Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving()){
5374                         break;
5375                 }
5376
5377                 // done!
5378                 if(Multi_pxo_ban_get->IsFileReceived()){
5379                         delete Multi_pxo_ban_get;
5380                         Multi_pxo_ban_get = NULL;
5381                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_STARTUP;
5382                 }
5383                 break;
5384
5385         // start downloading files
5386         case PXO_BAN_MODE_IMAGES_STARTUP:
5387                 // first thing - parse the banners file and pick a file
5388                 multi_pxo_ban_parse_banner_file(0);
5389
5390                 // if we have no active file, we're done
5391                 if((strlen(Multi_pxo_banner.ban_file) <= 0) || (strlen(Multi_pxo_banner.ban_file_url) <= 0)){
5392                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5393                         break;
5394                 }
5395
5396                 // if the file already exists, we're done
5397                 if(cf_exist(Multi_pxo_banner.ban_file, CF_TYPE_MULTI_CACHE)){
5398                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5399                         break;
5400                 }
5401
5402                 // otherwise try and download it                                
5403                 cf_create_default_path_string(local_file, CF_TYPE_MULTI_CACHE, Multi_pxo_banner.ban_file);
5404                 // try creating the file get object
5405                 Multi_pxo_ban_get = new InetGetFile(Multi_pxo_banner.ban_file_url, local_file);
5406
5407                 // bad
5408                 if(Multi_pxo_ban_get == NULL){                  
5409                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5410                 }
5411
5412                 // go to the downloading images mode
5413                 Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES;
5414                 break;
5415
5416         // downloading files
5417         case PXO_BAN_MODE_IMAGES:
5418                 // error
5419                 if(Multi_pxo_ban_get->IsFileError()){                   
5420                         delete Multi_pxo_ban_get;
5421                         Multi_pxo_ban_get = NULL;
5422                         Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5423                         break;
5424                 } 
5425
5426                 // connecting, receiving
5427                 if(Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving()){
5428                         break;
5429                 }
5430
5431                 // done!
5432                 if(Multi_pxo_ban_get->IsFileReceived()){
5433                         delete Multi_pxo_ban_get;
5434                         Multi_pxo_ban_get = NULL;
5435                         Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5436                 }
5437                 break;
5438
5439         // done downloading - maybe load an image
5440         case PXO_BAN_MODE_IMAGES_DONE:
5441                 // make sure we have a valid filename
5442                 // SDL_assert(strlen(Multi_pxo_banner.ban_file) > 0);
5443                 if(strlen(Multi_pxo_banner.ban_file) > 0){
5444                         Multi_pxo_banner.ban_bitmap = bm_load(Multi_pxo_banner.ban_file);
5445                 }
5446
5447                 // now we're idle
5448                 Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
5449                 break;
5450
5451         // idle (done with EVERYTHING)
5452         case PXO_BAN_MODE_IDLE:
5453                 // if the banner button was clicked
5454                 if(Multi_pxo_ban_button.pressed()){
5455                         multi_pxo_ban_clicked();                        
5456                 }
5457                 break;
5458
5459         case PXO_BAN_MODE_CHOOSE_RANDOM:
5460                 // first thing - parse the banners file and pick a file
5461                 multi_pxo_ban_parse_banner_file(1);
5462
5463                 Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
5464                 break;
5465         }
5466 }
5467
5468 // close
5469 void multi_pxo_ban_close()
5470 {
5471         // if we have a currently active transfer
5472         if(Multi_pxo_ban_get != NULL){
5473                 Multi_pxo_ban_get->AbortGet();
5474                 delete Multi_pxo_ban_get;
5475                 Multi_pxo_ban_get = NULL;
5476         }
5477
5478         // if we have a loaded bitmap, unload it
5479         if(Multi_pxo_banner.ban_bitmap != -1){
5480                 bm_unload(Multi_pxo_banner.ban_bitmap);
5481                 Multi_pxo_banner.ban_bitmap = -1;
5482         }
5483 }
5484
5485 // parse the banners file and maybe fill in Multi_pxo_dl_file
5486 void multi_pxo_ban_parse_banner_file(int choose_existing)
5487 {
5488         char file_url[512] = "";
5489         char banners[10][512];
5490         char urls[10][512];
5491         int exists[10];
5492         int exist_count;
5493         int num_banners, idx;
5494         CFILE *in = cfopen(PXO_BANNERS_CONFIG_FILE, "rt", CFILE_NORMAL, CF_TYPE_MULTI_CACHE);
5495
5496         Multi_pxo_banner.ban_bitmap = -1;
5497         strcpy(Multi_pxo_banner.ban_file, "");
5498         strcpy(Multi_pxo_banner.ban_file_url, "");
5499         strcpy(Multi_pxo_banner.ban_url, "");           
5500
5501         // bad
5502         if(in == NULL){
5503                 return;
5504         }
5505
5506         // clear all strings
5507         for(idx=0; idx<10; idx++){
5508                 strcpy(banners[idx], "");
5509                 strcpy(urls[idx], "");
5510         }
5511
5512         // get the global banner url
5513         if(cfgets(file_url, 254, in) == NULL){
5514                 cfclose(in);
5515                 cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE);
5516                 return;
5517         }
5518         drop_leading_white_space(file_url);
5519         drop_trailing_white_space(file_url);
5520
5521         // otherwise read in            
5522         num_banners = 0;
5523         while(num_banners < 10){
5524                 // try and get the pcx
5525                 if(cfgets(banners[num_banners], 254, in) == NULL){
5526                         break;
5527                 }
5528                 // try and get the url
5529                 if(cfgets(urls[num_banners], 254, in) == NULL){
5530                         break;
5531                 }
5532
5533                 // strip off trailing and leading whitespace
5534                 drop_leading_white_space(banners[num_banners]);
5535                 drop_trailing_white_space(banners[num_banners]);
5536                 drop_leading_white_space(urls[num_banners]);
5537                 drop_trailing_white_space(urls[num_banners]);
5538
5539                 // got one
5540                 num_banners++;          
5541         }
5542
5543         // close the file
5544         cfclose(in);
5545
5546         // no banners
5547         if(num_banners <= 0){           
5548                 return;
5549         }
5550
5551         // if we're only selecting files which already exist (previously downloaded)
5552         if(choose_existing){
5553                 // non exist
5554                 for(idx=0; idx<10; idx++){
5555                         exists[idx] = 0;
5556                 }
5557
5558                 // build a list of existing files
5559                 exist_count = 0;
5560                 for(idx=0; idx<num_banners; idx++){
5561                         if(cf_exist(banners[idx], CF_TYPE_MULTI_CACHE)){
5562                                 exists[idx] = 1;
5563                                 exist_count++;
5564                         }
5565                 }
5566
5567                 // bogus
5568                 if(exist_count <= 0){
5569                         return;
5570                 }
5571
5572                 // select one
5573                 int select = (int)frand_range(0.0f, (float)exist_count);
5574                 if(select >= exist_count){
5575                         select = exist_count - 1;
5576                 }
5577                 if(select < 0){
5578                         select = 0;
5579                 }
5580                 for(idx=0; idx<exist_count; idx++){
5581                         if(select == 0){
5582                                 break;
5583                         }
5584                         if(exists[idx]){
5585                                 select--;
5586                         }
5587                 }
5588
5589                 // valid?
5590                 if(idx < exist_count){
5591                         // base filename
5592                         strncpy(Multi_pxo_banner.ban_file, banners[idx], MAX_FILENAME_LEN);
5593
5594                         // get the full file url
5595                         strncpy(Multi_pxo_banner.ban_file_url, file_url, MULTI_OPTIONS_STRING_LEN);
5596                         strncat(Multi_pxo_banner.ban_file_url, banners[idx], MULTI_OPTIONS_STRING_LEN);
5597
5598                         // url of where to go to when clicked
5599                         strncpy(Multi_pxo_banner.ban_url, urls[idx], MULTI_OPTIONS_STRING_LEN);         
5600                 }
5601         }
5602         // randomly pick a file for download
5603         else {                  
5604                 idx = (int)frand_range(0.0f, (float)num_banners);
5605                 
5606                 if(idx >= num_banners){
5607                         idx = num_banners - 1;
5608                 } 
5609                 if(idx < 0){
5610                         idx = 0;
5611                 }
5612
5613                 // base filename
5614                 strncpy(Multi_pxo_banner.ban_file, banners[idx], MAX_FILENAME_LEN);
5615
5616                 // get the full file url
5617                 strncpy(Multi_pxo_banner.ban_file_url, file_url, MULTI_OPTIONS_STRING_LEN);
5618                 strncat(Multi_pxo_banner.ban_file_url, banners[idx], MULTI_OPTIONS_STRING_LEN);
5619
5620                 // url of where to go to when clicked
5621                 strncpy(Multi_pxo_banner.ban_url, urls[idx], MULTI_OPTIONS_STRING_LEN);         
5622         }
5623
5624         // delete the banner config file
5625         // cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE);
5626 }
5627
5628 // any bitmap or info or whatever
5629 void multi_pxo_ban_draw()
5630 {       
5631         // if we have a valid bitmap
5632         if(Multi_pxo_banner.ban_bitmap >= 0){
5633                 // if the mouse is over the banner button, highlight with a rectangle
5634                 if(Multi_pxo_ban_button.is_mouse_on()){
5635                         gr_set_color_fast(&Color_bright_blue);
5636                         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);
5637                 }
5638
5639                 // draw the bitmap itself
5640                 gr_set_bitmap(Multi_pxo_banner.ban_bitmap);
5641                 gr_bitmap(Pxo_ban_coords[gr_screen.res][0], Pxo_ban_coords[gr_screen.res][1]);
5642         }
5643 }
5644
5645 // called when the URL button is clicked
5646 void multi_pxo_ban_clicked()
5647 {
5648         // if we have a valid bitmap and URL, launch the URL
5649         if((Multi_pxo_banner.ban_bitmap >= 0) && (strlen(Multi_pxo_banner.ban_url) > 0)){
5650                 multi_pxo_url(Multi_pxo_banner.ban_url);
5651         }
5652 }
5653