/* * Copyright (C) Volition, Inc. 2005. All rights reserved. * * All source code herein is the property of Volition, Inc. You may not sell * or otherwise commercially exploit the source or things you created based on the * source. * */ #ifdef _WIN32 #include #endif #include "network/multi_pxo.h" #include "anim/animplay.h" #include "ui/ui.h" #include "io/key.h" #include "bmpman/bmpman.h" #include "gamesnd/gamesnd.h" #include "gamesequence/gamesequence.h" #include "cfile/cfile.h" #include "network/chat_api.h" #include "popup/popup.h" #include "freespace.h" #include "graphics/font.h" #include "network/multi.h" #include "network/multiui.h" #include "network/multi_log.h" #include "stats/medals.h" #include "globalincs/alphacolors.h" #include "graphics/2d.h" #include "graphics/generic.h" #include "io/timer.h" #include "inetfile/inetgetfile.h" #include "cfile/cfilesystem.h" #include "osapi/osregistry.h" #include "parse/parselo.h" #include "stats/scoring.h" #include "playerman/player.h" #include "network/multi_fstracker.h" #include "network/ptrack.h" #include "network/gtrack.h" #include "menuui/mainhallmenu.h" #include "debugconsole/console.h" // ---------------------------------------------------------------------------------------------------- // PXO DEFINES/VARS // #define MAX_PXO_TEXT_LEN 255 // button definitions #define MULTI_PXO_NUM_BUTTONS 15 #define MULTI_PXO_PLIST_UP 0 #define MULTI_PXO_PLIST_DOWN 1 #define MULTI_PXO_RANKINGS 2 #define MULTI_PXO_PINFO 3 #define MULTI_PXO_FIND 4 #define MULTI_PXO_MOTD 5 #define MULTI_PXO_JOIN 6 #define MULTI_PXO_JOIN_PRIV 7 #define MULTI_PXO_CHAN_UP 8 #define MULTI_PXO_CHAN_DOWN 9 #define MULTI_PXO_TEXT_UP 10 #define MULTI_PXO_TEXT_DOWN 11 #define MULTI_PXO_EXIT 12 #define MULTI_PXO_HELP 13 #define MULTI_PXO_GAMES 14 ui_button_info Multi_pxo_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_NUM_BUTTONS] = { { // GR_640 ui_button_info( "PXB_00", 1, 104, -1, -1, 0 ), // scroll player list up ui_button_info( "PXB_01", 1, 334, -1, -1, 1 ), // scroll player list down ui_button_info( "PXB_02", 18, 385, -1, -1, 2 ), // rankings webpage ui_button_info( "PXB_03", 71, 385, -1, -1, 3 ), // pilot info ui_button_info( "PXB_04", 115, 385, -1, -1, 4 ), // find player ui_button_info( "PXB_05", 1, 443, -1, -1, 5 ), // motd ui_button_info( "PXB_06", 330, 96, -1, -1, 6 ), // join channel ui_button_info( "PXB_07", 330, 131, -1, -1, 7 ), // join private channel ui_button_info( "PXB_08", 618, 92, -1, -1, 8 ), // scroll channels up ui_button_info( "PXB_09", 618, 128, -1, -1, 9 ), // scroll channels down ui_button_info( "PXB_10", 615, 171, -1, -1, 10 ), // scroll text up ui_button_info( "PXB_11", 615, 355, -1, -1, 11 ), // scroll text down ui_button_info( "PXB_12", 482, 435, -1, -1, 12 ), // exit ui_button_info( "PXB_13", 533, 432, -1, -1, 13 ), // help ui_button_info( "PXB_14", 573, 432, -1, -1, 14 ), // games list }, { // GR_1024 ui_button_info( "2_PXB_00", 2, 166, -1, -1, 0 ), // scroll player list up ui_button_info( "2_PXB_01", 2, 534, -1, -1, 1 ), // scroll player list down ui_button_info( "2_PXB_02", 29, 616, -1, -1, 2 ), // rankings webpage ui_button_info( "2_PXB_03", 114, 616, -1, -1, 3 ), // pilot info ui_button_info( "2_PXB_04", 184, 616, -1, -1, 4 ), // find player ui_button_info( "2_PXB_05", 2, 709, -1, -1, 5 ), // motd ui_button_info( "2_PXB_06", 528, 119, -1, -1, 6 ), // join channel ui_button_info( "2_PXB_07", 528, 175, -1, -1, 7 ), // join private channel ui_button_info( "2_PXB_08", 989, 112, -1, -1, 8 ), // scroll channels up ui_button_info( "2_PXB_09", 989, 170, -1, -1, 9 ), // scroll channels down ui_button_info( "2_PXB_10", 984, 240, -1, -1, 10 ), // scroll text up ui_button_info( "2_PXB_11", 984, 568, -1, -1, 11 ), // scroll text down ui_button_info( "2_PXB_12", 771, 696, -1, -1, 12 ), // exit ui_button_info( "2_PXB_13", 853, 691, -1, -1, 13 ), // help ui_button_info( "2_PXB_14", 917, 691, -1, -1, 14 ), // games list }, }; #define MULTI_PXO_NUM_TEXT 16 UI_XSTR Multi_pxo_text[GR_NUM_RESOLUTIONS][MULTI_PXO_NUM_TEXT] = { { // GR_640 {"Web", 1313, 20, 415, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_RANKINGS].button}, {"Ranking", 1314, 6, 426, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_RANKINGS].button}, {"Pilot", 1310, 68, 415, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_PINFO].button}, {"Info", 1311, 72, 426, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_PINFO].button}, {"Find", 1315, 119, 415, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_FIND].button}, {"Motd", 1316, 36, 456, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_MOTD].button}, {"Join", 1505, 291, 100, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_JOIN].button}, {"Channel", 1317, 266, 112, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_JOIN].button}, {"Join", 1506, 291, 134, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_JOIN_PRIV].button}, {"Private", 1318, 273, 146, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_JOIN_PRIV].button}, {"Exit", 1416, 493, 424, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_EXIT].button}, {"Help", 928, 535, 416, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[0][MULTI_PXO_HELP].button}, {"Games", 1319, 579, 416, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[0][MULTI_PXO_GAMES].button}, {"Players", 1269, 29, 102, UI_XSTR_COLOR_GREEN, -1, NULL}, {"Players", 1269, 507, 90, UI_XSTR_COLOR_GREEN, -1, NULL}, {"Games", 1319, 568, 90, UI_XSTR_COLOR_GREEN, -1, NULL} }, { // GR_1024 {"Web", 1313, 32, 664, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_RANKINGS].button}, {"Ranking", 1314, 9, 674, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_RANKINGS].button}, {"Pilot", 1310, 109, 664, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_PINFO].button}, {"Info", 1311, 115, 674, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_PINFO].button}, {"Find", 1315, 190, 664, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_FIND].button}, {"Motd", 1316, 58, 729, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_MOTD].button}, {"Join", 1505, 488, 129, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_JOIN].button}, {"Channel", 1317, 461, 139, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_JOIN].button}, {"Join", 1506, 487, 184, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_JOIN_PRIV].button}, {"Private", 1318, 467, 194, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_JOIN_PRIV].button}, {"Exit", 1416, 789, 678, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_EXIT].button}, {"Help", 928, 857, 667, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_buttons[1][MULTI_PXO_HELP].button}, {"Games", 1319, 917, 667, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_buttons[1][MULTI_PXO_GAMES].button}, {"Players", 1269, 47, 163, UI_XSTR_COLOR_GREEN, -1, NULL}, {"Players", 1269, 852, 109, UI_XSTR_COLOR_GREEN, -1, NULL}, {"Games", 1319, 926, 109, UI_XSTR_COLOR_GREEN, -1, NULL} } }; char Multi_pxo_bitmap_fname[GR_NUM_RESOLUTIONS][MAX_FILENAME_LEN] = { "PXOChat", "2_PXOChat" }; char Multi_pxo_mask_fname[GR_NUM_RESOLUTIONS][MAX_FILENAME_LEN] = { "PXOChat-M", "2_PXOChat-M" }; UI_WINDOW Multi_pxo_window; int Multi_pxo_bitmap = -1; int Multi_pxo_palette = -1; // pxo animation #define MULTI_PXO_ANIM_FNAME "pxologo" #define MULTI_PXO_ANIM_X 0 #define MULTI_PXO_ANIM_Y 4 generic_anim Multi_pxo_anim; // rankings last clicked time #define MULTI_PXO_RANK_TIME (5.0f) float Multi_pxo_ranking_last = -1.0f; // chat api vars int Multi_pxo_must_connect = 0; // if we still need to connect int Multi_pxo_connected = 0; // if we are connected int Multi_pxo_must_validate = 0; // if we need to validate on the tracker int Multi_pxo_must_autojoin = 1; // still need to autojoin a channel // mode #define MULTI_PXO_MODE_NORMAL 0 // normal mode #define MULTI_PXO_MODE_PRIVATE 1 // private channel popup #define MULTI_PXO_MODE_FIND 2 // find player popup int Multi_pxo_mode = MULTI_PXO_MODE_NORMAL; // our nick for this session char Multi_pxo_nick[NAME_LENGTH+1]; // check for button presses void multi_pxo_check_buttons(); // handle a button press void multi_pxo_button_pressed(int n); // condition function for popup_do_with_condition for connected to Parallax Online // return 10 : on successful connect int multi_pxo_connect_do(); // attempt to connect to Parallax Online, return success or fail int multi_pxo_connect(); // run the networking functions for the PXO API void multi_pxo_api_process(); // process a "nick" change event void multi_pxo_process_nick_change(char *data); // run normally (no popups) void multi_pxo_do_normal(); // blit everything on the "normal" screen void multi_pxo_blit_all(); // process common stuff void multi_pxo_process_common(); // get selected player information void multi_pxo_get_data(char *name); // handle being kicked void multi_pxo_handle_kick(); // handle being disconnected void multi_pxo_handle_disconnect(); // return string2, which is the first substring of string 1 without a space // it is safe to pass the same pointer for both parameters void multi_pxo_strip_space(char *string1,char *string2); // fire up the given URL void multi_pxo_url(char *url); // unload the palette void multi_pxo_unload_palette(); // if we're currently on a private channel int multi_pxo_on_private_channel(); // convert string 1 into string 2, substituting underscores for spaces void multi_pxo_underscore_nick(char *string1,char *string2); // if the command is a potential "nick" command int multi_pxo_is_nick_command(char *msg); // status bar stuff ----------------------------------------------- int Multi_pxo_status_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 95, 467, 354, 12 }, { // GR_1024 152, 750, 570, 12 }, }; // the status text itself char Multi_pxo_status_text[MAX_PXO_TEXT_LEN]; // set the status text void multi_pxo_set_status_text(const char *txt); // blit the status text void multi_pxo_blit_status_text(); // channel related stuff ------------------------------------------- #define MAX_CHANNEL_NAME_LEN 32 #define MAX_CHANNEL_DESCRIPT_LEN 120 // some convenient macros #define SWITCHING_CHANNELS() (Multi_pxo_channel_switch.num_users != -1) #define ON_CHANNEL() (Multi_pxo_channel_current.num_users != -1) typedef struct pxo_channel { pxo_channel *next,*prev; // next and previous items in the list char name[MAX_CHANNEL_NAME_LEN+1]; // name char desc[MAX_CHANNEL_DESCRIPT_LEN+1]; // description short num_users; // # users, or -1 if not in use short num_servers; // the # of servers registered on this channel } pxo_channel; // last channel we were on before going to the game list screen char Multi_pxo_channel_last[MAX_CHANNEL_NAME_LEN+1] = ""; int Multi_pxo_use_last_channel = 0; // all channels which are prefixed with this are "lobby" channels #define MULTI_PXO_AUTOJOIN_PREFIX "#lobby" // join this channel to get put in an appropriate lobby channel #define MULTI_PXO_AUTOJOIN_CHANNEL "#autoselect" int Multi_pxo_chan_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 369, 101, 241, 60 }, { // GR_1024 593, 124, 386, 100 }, }; // this is the offset from the RIGHT side of the channel box #define CHAN_PLAYERS_COLUMN 0 #define CHAN_GAMES_COLUMN 1 static int Multi_pxo_chan_column_offsets[GR_NUM_RESOLUTIONS][2] = { { 81, 26 }, { 103, 35 } }; #define CHANNEL_REFRESH_TIME (75.0f) float Multi_pxo_channel_last_refresh = -1.0f; #define CHANNEL_SERVER_REFRESH_TIME (35.0f) float Multi_pxo_channel_server_refresh = -1.0f; int Multi_pxo_max_chan_display[GR_NUM_RESOLUTIONS] = { 6, // GR_640 10 // GR_1024 }; UI_BUTTON Multi_pxo_channel_button; // head of the list of available (displayed) channels pxo_channel *Multi_pxo_channels = NULL; int Multi_pxo_channel_count = 0; // item we're going to start displaying at pxo_channel *Multi_pxo_channel_start = NULL; int Multi_pxo_channel_start_index = -1; // items we've currently got selected pxo_channel *Multi_pxo_channel_select = NULL; // channel we're currently connected to, num_users == -1, if we're not connected pxo_channel Multi_pxo_channel_current; // channel we're currently trying to change to, num_users == -1, if we're not trying to change channels pxo_channel Multi_pxo_channel_switch; // get a list of channels on the server (clear any old list as well) void multi_pxo_get_channels(); // clear the old channel list void multi_pxo_clear_channels(); // parse the input string and make a list of new channels void multi_pxo_make_channels(char *chan_str); // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail pxo_channel *multi_pxo_add_channel(char *name, pxo_channel **list); // lookup a channel with the specified name pxo_channel *multi_pxo_find_channel(char *name, pxo_channel *list); // process the channel list (select, etc) void multi_pxo_process_channels(); // display the channel list void multi_pxo_blit_channels(); // scroll channel list up void multi_pxo_scroll_channels_up(); // scroll channel list down void multi_pxo_scroll_channels_down(); // attempt to join a channel void multi_pxo_join_channel(pxo_channel *chan); // handle any processing details if we're currently trying to join a channel void multi_pxo_handle_channel_change(); // autojoin an appropriate channel void multi_pxo_autojoin(); // does the string match the "autojoin" prefic int multi_pxo_is_autojoin(char *name); // send a request to refresh our channel server counts void multi_pxo_channel_refresh_servers(); // refresh current channel server count void multi_pxo_channel_refresh_current(); // player related stuff ------------------------------------------- #define MAX_PLAYER_NAME_LEN 32 typedef struct player_list { player_list *next,*prev; char name[MAX_PLAYER_NAME_LEN+1]; } player_list; // channel list region int Multi_pxo_player_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 27, 121, 141, 261 }, { // GR_1024 43, 194, 154, 417 }, }; int Multi_pxo_max_player_display[GR_NUM_RESOLUTIONS] = { 25, // GR_640 41 // GR_1024 }; UI_BUTTON Multi_pxo_player_button; // slider coords int Multi_pxo_player_slider_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 1, 139, 21, 192 }, { // GR_1024 2, 219, 33, 314 } }; const char *Multi_pxo_player_slider_name[GR_NUM_RESOLUTIONS] = { "slider", // GR_640 "2_slider" // GR_1024 }; // head of the list of players in this channel player_list *Multi_pxo_players = NULL; int Multi_pxo_player_count = 0; // item we're going to start displaying at player_list *Multi_pxo_player_start = NULL; // int Multi_pxo_player_start_index = -1; // items we've currently got selected player_list *Multi_pxo_player_select = NULL; // clear the old player list void multi_pxo_clear_players(); // create a new player with the given name and place it on the player list, return a pointer or NULL on fail player_list *multi_pxo_add_player(char *name); // remove a player with the given name void multi_pxo_del_player(char *name); // try and find a player with the given name, return a pointer to his entry (or NULL) player_list *multi_pxo_find_player(char *name); // process the player list (select, etc) void multi_pxo_process_players(); // display the player list void multi_pxo_blit_players(); // scroll player list up void multi_pxo_scroll_players_up(); // scroll player list down void multi_pxo_scroll_players_down(); // get the absolute index of the displayed items which our currently selected one is int multi_pxo_get_select_index(); DCF(players, "Adds the specified number of bogus players to the PXO listing (Multiplayer)") { char name[512] = ""; int i; // add a bunch of bogus players dc_stuff_int(&i); for(int idx = 0; idx < i; idx++){ sprintf(name, "bogus player %d", idx); multi_pxo_add_player(name); } } // chat text stuff ----------------------------------------- #define MAX_CHAT_LINES 60 #define MAX_CHAT_LINE_LEN 256 int Multi_pxo_chat_title_y[GR_NUM_RESOLUTIONS] = { 181, // GR_640 253 // GR_1024 }; int Multi_pxo_chat_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 196, 197, 412, 185 }, { // GR_1024 314, 271, 665, 330 } }; int Multi_pxo_input_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 196, 386, 407, 24 }, { // GR_1024 314, 617, 660, 38 } }; int Multi_pxo_max_chat_display[GR_NUM_RESOLUTIONS] = { 17, // GR_640 32 // GR_1024 }; // all messages from the server are prefixed with this #define MULTI_PXO_SERVER_PREFIX "*** " // the "has left" message from the server #define MULTI_PXO_HAS_LEFT "has left" // chat flags #define CHAT_MODE_NORMAL 0 // normal chat from someone #define CHAT_MODE_SERVER 1 // is from the server, display appropriately #define CHAT_MODE_CARRY 2 // is a carryover from a previous line #define CHAT_MODE_PRIVATE 3 // is a private message #define CHAT_MODE_CHANNEL_SWITCH 4 // "switching channels" message - draw in red #define CHAT_MODE_MOTD 5 // message of the day from the chat server typedef struct chat_line { chat_line *next,*prev; char text[MAX_CHAT_LINE_LEN+1]; int mode; } chat_line; // the chat linked list itself chat_line *Multi_pxo_chat = NULL; // the current add line chat_line *Multi_pxo_chat_add = NULL; // the current line to start displaying from chat_line *Multi_pxo_chat_start = NULL; int Multi_pxo_chat_start_index = -1; // input box for text UI_INPUTBOX Multi_pxo_chat_input; // slider for chat UI_SLIDER2 Multi_pxo_chat_slider; int Multi_pxo_chat_slider_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 620, 206, 21, 147 }, { // GR_1024 990, 295, 34, 269 } }; const char *Multi_pxo_chat_slider_name[GR_NUM_RESOLUTIONS] = { "slider", "2_slider" }; // how many chat lines we have int Multi_pxo_chat_count = 0; // extra delay time when switching channels #define MULTI_PXO_SWITCH_DELAY_TIME 2000 int Multi_pxo_switch_delay = -1; // initialize and create the chat text linked list void multi_pxo_chat_init(); // free up all chat list stuff void multi_pxo_chat_free(); // clear all lines of chat text in the chat area void multi_pxo_chat_clear(); // blit the chat text void multi_pxo_chat_blit(); // add a line of text void multi_pxo_chat_add_line(const char *txt,int mode); // process an incoming line of text void multi_pxo_chat_process_incoming(const char *txt, int mode = CHAT_MODE_NORMAL); // scroll to the very bottom of the chat area void multi_pxo_goto_bottom(); // check whether we can scroll down or not int multi_pxo_can_scroll_down(); static int Can_scroll_down = 0; // scroll the text up void multi_pxo_scroll_chat_up(); // scroll the text down void multi_pxo_scroll_chat_down(); // process chat controls void multi_pxo_chat_process(); // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL const char *multi_pxo_chat_is_private(const char *txt); // if the text came from the server int multi_pxo_is_server_text(const char *txt); // if the text is message of the day text int multi_pxo_is_motd_text(const char *txt); // if the text is the end of motd text int multi_pxo_is_end_of_motd_text(const char *txt); // if the text is a "has left message" from the server int multi_pxo_chat_is_left_message(const char *txt); // recalculate the chat start index, and adjust the slider properly void multi_pxo_chat_adjust_start(); // motd stuff --------------------------------------------------------- #define MAX_PXO_MOTD_LEN 1024 #define PXO_MOTD_BLINK_TIME 500 char Pxo_motd[1024] = ""; int Pxo_motd_end = 0; int Pxo_motd_read = 0; int Pxo_motd_blink_stamp = -1; int Pxo_motd_blink_on = 0; int Pxo_motd_blinked_already = 0; // initialize motd when going into this screen void multi_pxo_motd_init(); // set the motd text void multi_pxo_motd_add_text(const char *text); // set end of motd void multi_pxo_set_end_of_motd(); // display the motd dialog void multi_pxo_motd_dialog(); // call to maybe blink the motd button void multi_pxo_motd_maybe_blit(); // common dialog stuff ------------------------------------------------ const char *Multi_pxo_com_fname[GR_NUM_RESOLUTIONS] = { "PXOPop", "2_PXOPop" }; const char *Multi_pxo_com_mask_fname[GR_NUM_RESOLUTIONS] = { "PXOPop-m", "2_PXOPop-m" }; // popup coords int Multi_pxo_com_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 38, 129 }, { // GR_1024 61, 207 } }; // input box coords int Multi_pxo_com_input_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 53, 233, 448, 25 }, { // GR_1024 85, 372, 716, 40 } }; #define MULTI_PXO_COM_NUM_BUTTONS 2 #define MULTI_PXO_COM_CANCEL 0 #define MULTI_PXO_COM_OK 1 ui_button_info Multi_pxo_com_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_COM_NUM_BUTTONS] = { { // GR_640 ui_button_info("PXP_00", 573, 192, -1, -1, 0), ui_button_info("PXP_01", 573, 226, -1, -1, 1) }, { // GR_1024 ui_button_info("2_PXP_00", 917, 308, -1, -1, 0), ui_button_info("2_PXP_01", 917, 361, -1, -1, 1) } }; #define MULTI_PXO_COM_NUM_TEXT 2 UI_XSTR Multi_pxo_com_text[GR_NUM_RESOLUTIONS][MULTI_PXO_COM_NUM_TEXT] = { { // GR_640 { "&Cancel", 645, 510, 204, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_com_buttons[0][MULTI_PXO_COM_CANCEL].button }, { "&Ok", 669, 548, 233, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_com_buttons[0][MULTI_PXO_COM_OK].button } }, { // GR_1024 { "&Cancel", 645, 847, 327, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_com_buttons[1][MULTI_PXO_COM_CANCEL].button }, { "&Ok", 669, 877, 372, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_com_buttons[1][MULTI_PXO_COM_OK].button } } }; int Multi_pxo_com_bitmap = -1; UI_WINDOW Multi_pxo_com_window; UI_INPUTBOX Multi_pxo_com_input; // text on the "top" half of the dialog display area char Multi_pxo_com_top_text[MAX_PXO_TEXT_LEN]; // text on the "middle" portion of the dialog display area char Multi_pxo_com_middle_text[MAX_PXO_TEXT_LEN]; // text on the "bottom" half of the dialog display area char Multi_pxo_com_bottom_text[MAX_PXO_TEXT_LEN]; int Multi_pxo_com_top_text_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 58, 152 }, { // GR_1024 91, 227 } }; int Multi_pxo_com_middle_text_y[GR_NUM_RESOLUTIONS] = { 172, // GR_640 280 // GR_1024 }; int Multi_pxo_com_bottom_text_y[GR_NUM_RESOLUTIONS] = { 192, // GR_640 326 // GR_1024 }; // initialize the common dialog with the passed max input length void multi_pxo_com_init(); // close down the common dialog void multi_pxo_com_close(); // blit all text lines, top, middle, bottoms void multi_pxo_com_blit_text(); // set the top text, shortening as necessary void multi_pxo_com_set_top_text(const char *txt); // set the middle text, shortening as necessary void multi_pxo_com_set_middle_text(const char *txt); // set the bottom text, shortening as necessary void multi_pxo_com_set_bottom_text(const char *txt); // private channel join stuff ----------------------------------------- #define MULTI_PXO_PRIV_MAX_TEXT_LEN 30 // max private channel name length char Multi_pxo_priv_chan[MULTI_PXO_PRIV_MAX_TEXT_LEN+100]; // return code, set to something other than -1 if we're supposed to return int Multi_pxo_priv_return_code = -1; // initialize the popup void multi_pxo_priv_init(); // close down the popup void multi_pxo_priv_close(); // run the popup, 0 if still running, -1 if cancel, 1 if ok int multi_pxo_priv_popup(); // process button presses void multi_pxo_priv_process_buttons(); // handle a button press void multi_pxo_priv_button_pressed(int n); // process the inputbox void multi_pxo_priv_process_input(); // find player stuff ----------------------------------------- char Multi_pxo_find_channel[MAX_CHANNEL_NAME_LEN+1]; // return code, set to something other than -1 if we're supposed to return int Multi_pxo_find_return_code = -1; // initialize the popup void multi_pxo_find_init(); // close down the popup void multi_pxo_find_close(); // run the popup, 0 if still running, -1 if cancel, 1 if ok int multi_pxo_find_popup(); // process button presses void multi_pxo_find_process_buttons(); // handle a button press void multi_pxo_find_button_pressed(int n); // process the inputbox void multi_pxo_find_process_input(); // process search mode if applicable void multi_pxo_find_search_process(); // player info stuff ----------------------------------------- const char *Multi_pxo_pinfo_fname[GR_NUM_RESOLUTIONS] = { "PilotInfo2", "2_PilotInfo2" }; const char *Multi_pxo_pinfo_mask_fname[GR_NUM_RESOLUTIONS] = { "PilotInfo2-M", "2_PilotInfo2-M" }; // medals #define MULTI_PXO_PINFO_NUM_BUTTONS 2 #define MULTI_PXO_PINFO_MEDALS 0 #define MULTI_PXO_PINFO_OK 1 ui_button_info Multi_pxo_pinfo_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_PINFO_NUM_BUTTONS] = { { // GR_640 ui_button_info("PI2_00", 328, 446, 319, 433, 0), ui_button_info("PI2_01", 376, 446, 382, 433, 1), }, { // GR_1024 ui_button_info("2_PI2_00", 525, 714, 510, 695, 0), ui_button_info("2_PI2_01", 601, 714, 611, 695, 1), } }; // text #define MULTI_PXO_PINFO_NUM_TEXT 2 UI_XSTR Multi_pxo_pinfo_text[GR_NUM_RESOLUTIONS][MULTI_PXO_PINFO_NUM_TEXT] = { { // GR_640 { "Medals", 1037, 319, 433, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_pinfo_buttons[0][MULTI_PXO_PINFO_MEDALS].button }, { "Ok", 345, 382, 433, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_pinfo_buttons[0][MULTI_PXO_PINFO_OK].button }, }, { // GR_1024 { "Medals", 1037, 510, 695, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_pinfo_buttons[1][MULTI_PXO_PINFO_MEDALS].button }, { "Ok", 345, 611, 695, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_pinfo_buttons[1][MULTI_PXO_PINFO_OK].button }, } }; int Multi_pxo_pinfo_bitmap = -1; UI_WINDOW Multi_pxo_pinfo_window; vmt_stats_struct Multi_pxo_pinfo; player Multi_pxo_pinfo_player; int Multi_pxo_retrieve_mode = -1; char Multi_pxo_retrieve_name[MAX_PLAYER_NAME_LEN+1]; char Multi_pxo_retrieve_id[128]; // stats label stuff #define MULTI_PXO_PINFO_NUM_LABELS 18 int Multi_pxo_pinfo_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 37, 142, 377, 289 }, { // GR_640 54, 227, 602, 462 }, }; int Multi_pxo_pinfo_val_x[GR_NUM_RESOLUTIONS] = { 230, // GR_640 310 // GR_1024 }; char *Multi_pxo_pinfo_stats_labels[MULTI_PXO_PINFO_NUM_LABELS]; char Multi_pxo_pinfo_vals[MULTI_PXO_PINFO_NUM_LABELS][50]; int Multi_pxo_pinfo_stats_spacing[MULTI_PXO_PINFO_NUM_LABELS] = { 10,20,10,10,20,10,10,20,10,10,20,10,10,20,10,20,10,0 }; // popup conditional functions, returns 10 on successful get of stats int multi_pxo_pinfo_cond(); // return 1 if Multi_pxo_pinfo was successfully filled in, 0 otherwise int multi_pxo_pinfo_get(char *name); // fire up the stats view popup void multi_pxo_pinfo_show(); // build the stats labels values void multi_pxo_pinfo_build_vals(); // initialize the popup void multi_pxo_pinfo_init(); // do frame int multi_pxo_pinfo_do(); // close void multi_pxo_pinfo_close(); // blit all the stats on this screen void multi_pxo_pinfo_blit(); // run the medals screen void multi_pxo_run_medals(); // notify stuff stuff ----------------------------------------- #define MULTI_PXO_NOTIFY_TIME 4000 #define MULTI_PXO_NOTIFY_Y 435 char Multi_pxo_notify_text[MAX_PXO_TEXT_LEN]; int Multi_pxo_notify_stamp = -1; // add a notification string void multi_pxo_notify_add(const char *txt); // blit and process the notification string void multi_pxo_notify_blit(); // help screen stuff ----------------------------------------- //XSTR:OFF const char *Multi_pxo_help_fname[GR_NUM_RESOLUTIONS] = { "PXHelp", "2_PXHelp" }; const char *Multi_pxo_help_mask_fname[GR_NUM_RESOLUTIONS] = { "PXOHelp-M", "2_PXOHelp-M" }; #define MULTI_PXO_HELP_NUM_BUTTONS 3 #define MULTI_PXO_HELP_PREV 0 #define MULTI_PXO_HELP_NEXT 1 #define MULTI_PXO_HELP_CONTINUE 2 ui_button_info Multi_pxo_help_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_HELP_NUM_BUTTONS] = { { // GR_640 ui_button_info("PXH_00", 15, 389, -1, -1, 0), ui_button_info("PXH_01", 60, 389, -1, -1, 1), ui_button_info("PXH_02", 574, 431, 571, 413, 2), }, { // GR_1024 ui_button_info("2_PXH_00", 24, 622, -1, -1, 0), ui_button_info("2_PXH_01", 96, 622, -1, -1, 1), ui_button_info("2_PXH_02", 919, 689, 928, 663, 2), } }; #define MULTI_PXO_HELP_NUM_TEXT 1 UI_XSTR Multi_pxo_help_text[GR_NUM_RESOLUTIONS][MULTI_PXO_HELP_NUM_TEXT] = { { // GR_640 {"Continue", 1069, 571, 413, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_help_buttons[0][MULTI_PXO_HELP_CONTINUE].button }, }, { // GR_1024 {"Continue", 1069, 928, 663, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_help_buttons[1][MULTI_PXO_HELP_CONTINUE].button }, }, }; // help text #define MULTI_PXO_HELP_FILE "pxohelp.txt" #define MULTI_PXO_MAX_LINES_PP 57 #define MULTI_PXO_MAX_PAGES 5 int Multi_pxo_help_coords[GR_NUM_RESOLUTIONS][2] = { { // GR_640 40, 40 }, { // GR_1024 60, 40 } }; int Multi_pxo_chars_per_line[GR_NUM_RESOLUTIONS] = { 130, // GR_640 130 // GR_1024 }; int Multi_pxo_lines_pp[GR_NUM_RESOLUTIONS] = { 32, // GR_640 57 // GR_1024 }; // help text pages typedef struct help_page { char *text[MULTI_PXO_MAX_LINES_PP]; int num_lines; } help_page; help_page Multi_pxo_help_pages[MULTI_PXO_MAX_PAGES]; int Multi_pxo_help_num_pages = 0; int Multi_pxo_help_bitmap = -1; UI_WINDOW Multi_pxo_help_window; // current page we're on int Multi_pxo_help_cur = 0; // load the help file up void multi_pxo_help_load(); // blit the current page void multi_pxo_help_blit_page(); // process button presses void multi_pxo_help_process_buttons(); // button pressed void multi_pxo_help_button_pressed(int n); // http banner stuff --------------------------------------------- InetGetFile *Multi_pxo_ban_get = NULL; // banners file #define PXO_BANNERS_CONFIG_FILE "pxobanners.cfg" // coords to display banners at int Pxo_ban_coords[GR_NUM_RESOLUTIONS][4] = { { // GR_640 149, 3, 475, 75 }, { // GR_1024 524, 3, 475, 75 } }; // http modes #define PXO_BAN_MODE_LIST_STARTUP 0 // start downloading list #define PXO_BAN_MODE_LIST 1 // downloading list #define PXO_BAN_MODE_IMAGES_STARTUP 2 // start downloading images #define PXO_BAN_MODE_IMAGES 3 // downloading images #define PXO_BAN_MODE_IMAGES_DONE 4 // done downloading everything - now maybe load an image #define PXO_BAN_MODE_IDLE 5 // done with everything - doing nothing #define PXO_BAN_MODE_CHOOSE_RANDOM 6 // choose a bitmap we've already downloaded at random // interface button for detecting clicks UI_BUTTON Multi_pxo_ban_button; // banners typedef struct pxo_banner { char ban_file[MAX_FILENAME_LEN+1]; // base filename of the banner char ban_file_url[MULTI_OPTIONS_STRING_LEN+1]; // full url of the file to get (convenient) char ban_url[MULTI_OPTIONS_STRING_LEN+1]; // url to go to when clicked int ban_bitmap; // banner bitmap } pxo_banner; // active pxo banner pxo_banner Multi_pxo_banner; // mode int Multi_pxo_ban_mode = PXO_BAN_MODE_LIST_STARTUP; // init void multi_pxo_ban_init(); // process http download details void multi_pxo_ban_process(); // close void multi_pxo_ban_close(); // parse the banners file and maybe fill in Multi_pxo_dl_file[] void multi_pxo_ban_parse_banner_file(int choose_existing); // any bitmap or info or whatever void multi_pxo_ban_draw(); // called when the URL button is clicked void multi_pxo_ban_clicked(); // ---------------------------------------------------------------------------------------------------- // PXO FUNCTIONS // // initialize the PXO screen void multi_pxo_init(int use_last_channel) { int idx; // load the background bitmap Multi_pxo_bitmap = bm_load(Multi_pxo_bitmap_fname[gr_screen.res]); if(Multi_pxo_bitmap < 0){ // we failed to load the bitmap - this is very bad Int3(); } // load up the private channel bitmap Multi_pxo_com_bitmap = bm_load(Multi_pxo_com_fname[gr_screen.res]); Assert(Multi_pxo_com_bitmap != -1); // create the interface window Multi_pxo_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0); Multi_pxo_window.set_mask_bmap(Multi_pxo_mask_fname[gr_screen.res]); // create the interface buttons for(idx=0;idxcallsign,Multi_pxo_nick); // clear the channel list multi_pxo_clear_channels(); // clear the player list multi_pxo_clear_players(); // initialize the chat system multi_pxo_chat_init(); // initialize http multi_pxo_ban_init(); // load the animation up if (gr_screen.res == GR_1024) { char anim_filename[32] = "2_"; strcat_s(anim_filename, MULTI_PXO_ANIM_FNAME); generic_anim_init(&Multi_pxo_anim, anim_filename); Multi_pxo_anim.ani.bg_type = bm_get_type(Multi_pxo_bitmap); // if hi-res is not there, fallback to low if (generic_anim_stream(&Multi_pxo_anim) == -1) { generic_anim_init(&Multi_pxo_anim, MULTI_PXO_ANIM_FNAME); generic_anim_stream(&Multi_pxo_anim); } } else { generic_anim_init(&Multi_pxo_anim, MULTI_PXO_ANIM_FNAME); Multi_pxo_anim.ani.bg_type = bm_get_type(Multi_pxo_bitmap); generic_anim_stream(&Multi_pxo_anim); } // clear the status text multi_pxo_set_status_text(""); // last refresh time Multi_pxo_channel_last_refresh = -1.0f; // server count last refresh time Multi_pxo_channel_server_refresh = -1.0f; // set our mode Multi_pxo_mode = MULTI_PXO_MODE_NORMAL; // init motd multi_pxo_motd_init(); // make sure we autojoin Multi_pxo_must_autojoin = 1; // clear all tracker channel related strings memset(Multi_fs_tracker_channel, 0, MAX_PATH); memset(Multi_fs_tracker_filter, 0, MAX_PATH); main_hall_start_music(); } // do frame for the PXO screen void multi_pxo_do() { pxo_channel priv_chan; // run api stuff if(Multi_pxo_connected) { multi_pxo_api_process(); } // process common stuff multi_pxo_process_common(); switch(Multi_pxo_mode){ // private channel join mode case MULTI_PXO_MODE_PRIVATE: switch(multi_pxo_priv_popup()){ // still running case 0: break; // user hit "cancel" case -1: // return to normal mode Multi_pxo_mode = MULTI_PXO_MODE_NORMAL; break; // user hit "ok" case 1 : // setup some information memset(&priv_chan, 0, sizeof(pxo_channel)); priv_chan.num_users = 0; strcpy_s(priv_chan.name, Multi_pxo_priv_chan); // see if we know about this channel already multi_pxo_join_channel(&priv_chan); // return to normal mode Multi_pxo_mode = MULTI_PXO_MODE_NORMAL; break; } break; // find player mode case MULTI_PXO_MODE_FIND: switch(multi_pxo_find_popup()){ // still running case 0: break; // user hit "cancel" case -1: // return to normal mode Multi_pxo_mode = MULTI_PXO_MODE_NORMAL; break; // user hit "ok" case 1 : // return to normal mode Multi_pxo_mode = MULTI_PXO_MODE_NORMAL; // if there is a valid channel name try and join it if(strlen(Multi_pxo_find_channel) && !SWITCHING_CHANNELS()){ pxo_channel join; // setup the info memset(&join,0,sizeof(pxo_channel)); join.num_users = 0; strcpy_s(join.name,Multi_pxo_find_channel); // try and join multi_pxo_join_channel(&join); } break; } break; // normal mode case MULTI_PXO_MODE_NORMAL: multi_pxo_do_normal(); break; } } //XSTR:ON // close the PXO screen void multi_pxo_close() { // unload any bitmaps bm_release(Multi_pxo_bitmap); bm_release(Multi_pxo_com_bitmap); // record the last channel we were on, if any memset(Multi_fs_tracker_channel, 0, MAX_PATH); memset(Multi_fs_tracker_filter, 0, MAX_PATH); if ( ON_CHANNEL() && strlen(Multi_pxo_channel_current.name) ) { // channel name strcpy(Multi_fs_tracker_channel, Multi_pxo_channel_current.name); // filter name strcpy(Multi_fs_tracker_filter, Multi_pxo_channel_current.name); } // disconnect from the server DisconnectFromChatServer(); Multi_pxo_connected = 0; // unload the animation if(Multi_pxo_anim.num_frames > 0){ generic_anim_unload(&Multi_pxo_anim); } // unload the palette for this screen multi_pxo_unload_palette(); // destroy the UI_WINDOW Multi_pxo_window.destroy(); // clear the channel list multi_pxo_clear_channels(); // close the chat system multi_pxo_chat_free(); // close http stuff multi_pxo_ban_close(); } // run normally (no popups) void multi_pxo_do_normal() { int validate_code; int k = Multi_pxo_window.process(); // process any keypresses switch (k) { case KEY_ESC: gamesnd_play_iface(InterfaceSounds::USER_SELECT); gameseq_post_event(GS_EVENT_MAIN_MENU); break; } // check for button presses multi_pxo_check_buttons(); // if we're not in a chatroom, disable and hide the chat input box if ( !ON_CHANNEL() ) { Multi_pxo_chat_input.hide(); Multi_pxo_chat_input.disable(); } else { Multi_pxo_chat_input.enable(); Multi_pxo_chat_input.unhide(); } // blit everything multi_pxo_blit_all(); // flip the page gr_flip(); // if we need to get tracker info for ourselves, do so if (Multi_pxo_must_validate) { // initialize the master tracker API for Freespace multi_fs_tracker_init(); // validate game data first, for initial game/mod ident multi_fs_tracker_validate_game_data(); // validate the current player with the master tracker (will create the pilot on the MT if necessary) validate_code = multi_fs_tracker_validate(0); if (validate_code != 1) { // show an error popup if it failed (not cancelled by the user) if (validate_code == 0) { 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))) { case 0: nprintf(("Network","PXO CANCEL\n")); // flip his "pxo" bit temporarily and push him to the join game screen Multi_options_g.pxo = 0; // Net_game_tcp_mode = NET_TCP; gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME); break; case 1: nprintf(("Network","PXO CREATE\n")); // fire up the given URL multi_pxo_url(Multi_options_g.pxo_create_url); break; case 2: nprintf(("Network","PXO VERIFY\n")); // fire up the given URL multi_pxo_url(Multi_options_g.pxo_verify_url); break; } } Multi_pxo_must_connect = 0; Multi_pxo_must_validate = 0; } // now we have to connect to PXO else { Multi_pxo_must_connect = 1; Multi_pxo_must_validate = 0; } } // if we need to connect, do so now if (Multi_pxo_must_connect) { // for now, just try once Multi_pxo_connected = multi_pxo_connect(); // if we successfully connected, send a request for a list of channels on the server if (Multi_pxo_connected) { multi_pxo_get_channels(); // set our status multi_pxo_set_status_text(XSTR("Retrieving Public Channels",939)); } else { // set our status multi_pxo_set_status_text(XSTR("Failed to connect to Parallax Online",940)); } // no longer need to connect Multi_pxo_must_connect = 0; } } // blit everything on the "normal" screen void multi_pxo_blit_all() { // draw the background, etc gr_reset_clip(); // GR_MAYBE_CLEAR_RES(Multi_pxo_bitmap); int bmap = Multi_pxo_bitmap; do { int bmw = -1; int bmh = -1; if(bmap != -1){ bm_get_info( bmap, &bmw, &bmh); if((bmw != gr_screen.max_w_unscaled) || (bmh != gr_screen.max_h_unscaled)){ gr_clear(); } } else { gr_clear(); } } while(false); if(Multi_pxo_bitmap != -1){ gr_set_bitmap(Multi_pxo_bitmap); gr_bitmap(0,0,GR_RESIZE_MENU); } Multi_pxo_window.draw(); // display the channel list multi_pxo_blit_channels(); // display the player list multi_pxo_blit_players(); // blit the chat text multi_pxo_chat_blit(); // blit the status text multi_pxo_blit_status_text(); // blit and process the notification string multi_pxo_notify_blit(); // any bitmap or info or whatever multi_pxo_ban_draw(); // draw any motd stuff multi_pxo_motd_maybe_blit(); // if we have a valid animation handle, play it // display the mission start countdown timer (if any) //anim_render_all(GS_STATE_MULTI_MISSION_SYNC,flFrametime); if(gameseq_get_state() == GS_STATE_PXO && Multi_pxo_anim.num_frames > 0) generic_anim_render(&Multi_pxo_anim, flFrametime, MULTI_PXO_ANIM_X, MULTI_PXO_ANIM_Y, true); } // process common stuff void multi_pxo_process_common() { // process the channel list (select, etc) multi_pxo_process_channels(); // process the player list (select, etc) multi_pxo_process_players(); // process chat controls multi_pxo_chat_process(); // process http download details multi_pxo_ban_process(); } // get selected player information void multi_pxo_get_data(char * /*name*/) { } // handle being kicked void multi_pxo_handle_kick() { // remove ourselves from the room memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel)); Multi_pxo_channel_current.num_users = -1; // clear text multi_pxo_chat_clear(); // clear the old player list multi_pxo_clear_players(); // add a notification string multi_pxo_notify_add(XSTR("You have been kicked",941)); } // handle being disconnected void multi_pxo_handle_disconnect() { ml_printf("PXO: Got DISCONNECT from server!"); if ( popup_active() ) { popup_change_text( XSTR("You have been disconnected from the server", 942) ); } else { popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("You have been disconnected from the server", 942)); gameseq_post_event(GS_EVENT_MAIN_MENU); } } // return string2, which is the first substring of string 1 without a space // it is safe to pass the same pointer for both parameters void multi_pxo_strip_space(char *string1,char *string2) { char midway[MAX_PXO_TEXT_LEN]; char *tok; // copy the original strcpy_s(midway,string1); tok = strtok(midway," "); if(tok != NULL){ strcpy(string2,tok); } else { strcpy(string2,""); } } // fire up the given URL void multi_pxo_url(char * /*url*/) { #if 0 // execute the shell command int r = (int) ShellExecute(NULL, NOX("open"), url, NULL, NULL, SW_SHOW); if (r < 32) { switch (r) { case 0: case ERROR_BAD_FORMAT: case SE_ERR_ACCESSDENIED: case SE_ERR_ASSOCINCOMPLETE: case SE_ERR_DDEBUSY: case SE_ERR_DDEFAIL: case SE_ERR_DDETIMEOUT: case SE_ERR_DLLNOTFOUND: case SE_ERR_OOM: case SE_ERR_SHARE: case SE_ERR_NOASSOC: case ERROR_FILE_NOT_FOUND: case ERROR_PATH_NOT_FOUND: popup(PF_USE_AFFIRMATIVE_ICON | PF_TITLE_RED | PF_TITLE_BIG,1,POPUP_OK,XSTR("Warning\nCould not locate/launch default Internet Browser",943)); break; } } #endif } /** * Unload the palette */ void multi_pxo_unload_palette() { // unload the palette if it exists if(Multi_pxo_palette != -1){ bm_release(Multi_pxo_palette); Multi_pxo_palette = -1; } } /** * If we're currently on a private channel */ int multi_pxo_on_private_channel() { // if we're connected to a channel with the "+" symbol on front if(ON_CHANNEL() && (Multi_pxo_channel_current.name[0] == '+')){ return 1; } // otherwise return falos return 0; } /** * Convert string 1 into string 2, substituting underscores for spaces */ void multi_pxo_underscore_nick(char *string1,char *string2) { char nick_temp[512]; char *tok; // don't do anything if we have bogus string if((string1 == NULL) || (string2 == NULL)){ return; } // copy the nickname memset(nick_temp,0,512); strcpy_s(nick_temp,string1); // get the first token tok = strtok(nick_temp," "); if(tok != NULL){ strcpy(string2,tok); // get the next token tok = strtok(NULL," "); while(tok != NULL){ if(tok != NULL){ strcat(string2,"_"); strcat(string2,tok); } tok = strtok(NULL," "); } } else { strcpy(string2,string1); } } /** * If the command is a potential "nick" command */ int multi_pxo_is_nick_command(char *msg) { char *tok; char tmp[512]; // get the first token in the message memset(tmp,0,512); strcpy_s(tmp,msg); tok = strtok(tmp," "); if(tok == NULL){ // can't be a nick message return 0; } return !stricmp(tok,NOX("/nick")); } /** * Check for button presses */ void multi_pxo_check_buttons() { int idx; // go through all buttons for(idx=0;idxname)){ // convert stats to player multi_stats_tracker_to_fs(&Multi_pxo_pinfo, &Multi_pxo_pinfo_player.stats); SDL_strlcpy(Multi_pxo_pinfo_player.callsign, Multi_pxo_pinfo.pilot_name, SDL_arraysize(Multi_pxo_pinfo_player.callsign)); // show the stats multi_pxo_pinfo_show(); } // if we didn't get stats for this guy. else { memset(stats,0,MAX_PXO_TEXT_LEN); sprintf(stats,XSTR("Could not get stats for %s\n(May not be a registered pilot)",946),Multi_pxo_player_select->name); popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,stats); } } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } break; case MULTI_PXO_RANKINGS: // make sure he doesn't click it too many times if((Multi_pxo_ranking_last < 0.0f) || ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_ranking_last) > MULTI_PXO_RANK_TIME) ){ gamesnd_play_iface(InterfaceSounds::USER_SELECT); // fire up the url multi_pxo_url(Multi_options_g.pxo_rank_url); // mark the time down Multi_pxo_ranking_last = f2fl(timer_get_fixed_seconds()); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } break; case MULTI_PXO_MOTD: // maybe fire up the pxo motd dialog multi_pxo_motd_dialog(); break; } } /** * Condition function for popup_do_with_condition for connected to Parallax Online */ int mpxo_failed = 0; int multi_pxo_connect_do() { int ret_code; char id_string[MAX_PXO_TEXT_LEN] = ""; char ip_string[MAX_PXO_TEXT_LEN] = ""; // if we already tried and failed, sit around until the user presses cancel if(!mpxo_failed){ // try and connect to the server Assert(Player); // build the tracker id string memset(id_string, 0, MAX_PXO_TEXT_LEN); sprintf_safe(id_string, "%s %s", Multi_tracker_id_string, Player->callsign); // build the ip string memset(ip_string, 0, MAX_PXO_TEXT_LEN); if (snprintf(ip_string, MAX_PXO_TEXT_LEN, "%s:%d", Multi_options_g.pxo_ip, PXO_CHAT_PORT) >= MAX_PXO_TEXT_LEN) { // Make sure the string is null terminated ip_string[MAX_PXO_TEXT_LEN - 1] = '\0'; } // connect to the server ret_code = ConnectToChatServer(ip_string, Multi_pxo_nick, id_string); // give some time to the pxo api. multi_pxo_api_process(); switch(ret_code){ // already connected, return success case -2: return 10; // failed to connect, return fail case -1 : mpxo_failed = 1; return 1; // connected, return success case 1 : return 10; // still connecting case 0 : return 0; } } return 0; } /** * Popup loop which does an autojoin of a public channel. * * Returns when the autojoin process is complete */ int multi_pxo_autojoin_do() { pxo_channel last_channel; // if we need to autojoin, do so now if (Multi_pxo_must_autojoin) { Multi_pxo_must_autojoin = 0; // if we're supposed to be using a (valid) "last" channel, do so if ( Multi_pxo_use_last_channel && strlen(Multi_pxo_channel_last) ) { // setup the data memset(&last_channel, 0, sizeof(pxo_channel)); last_channel.num_users = 0; strcpy_s(last_channel.name, Multi_pxo_channel_last); // join the channel multi_pxo_join_channel(&last_channel); nprintf(("Network","PXO : using last channel\n")); } else { multi_pxo_autojoin(); nprintf(("Network","PXO : using autojoin channel\n")); } multi_pxo_get_channels(); } // give some time to the pxo api. multi_pxo_api_process(); multi_pxo_process_common(); // next value is not -1 when actually switching channels, so keep processing by returning 0. if ( SWITCHING_CHANNELS() ) return 0; // couldn't switch channel for some reason. bail out with -1 if ( !ON_CHANNEL() ) return -1; // return success return 1; } /** * Attempt to connect to Parallax Online, return success or fail */ int multi_pxo_connect() { char join_str[256]; char join_fail_str[256]; // intiialize chat api ChatInit(); // set us to "must autojoin" Multi_pxo_must_autojoin = 1; // run the connect dialog/popup mpxo_failed = 0; if ( popup_till_condition(multi_pxo_connect_do, XSTR("&Cancel", 779), XSTR("Logging into Parallax Online", 949)) == 10 ) { int rval; memset(join_str, 0, 256); memset(join_fail_str, 0, 256); // if we're going to use the "last" channel if ( Multi_pxo_use_last_channel && strlen(Multi_pxo_channel_last) ) { strcpy_s(join_str, XSTR("Joining last channel (", 982)); strcat_s(join_str, Multi_pxo_channel_last + 1); strcat_s(join_str, ")"); strcpy_s(join_fail_str, XSTR("Unable to join last channel", 983)); } else { strcpy_s(join_str, XSTR("Autojoining public channel", 984)); strcpy_s(join_fail_str, XSTR("Unable to autojoin public channel", 985)); } // once connected, we should do an autojoin before allowing the guy to continue. rval = popup_till_condition( multi_pxo_autojoin_do, XSTR("&Cancel", 779), join_str ); if ( rval == 1 ) return 1; popup(PF_USE_AFFIRMATIVE_ICON, 1, XSTR("OK", 1492), join_fail_str); } // otherwise disconnect just to be safe DisconnectFromChatServer(); // we failed to connect, so give a nice popup about that if (mpxo_failed) { popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("Failed to connect to Parallax Online!", 947)); } // if we are coming from the mainhall then fail to the join game screen rather // than keeping the user constantly at the mainhall if (gameseq_get_previous_state() == GS_STATE_MAIN_MENU) { gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME); } else { gameseq_post_event(GS_EVENT_MAIN_MENU); } // did not successfully connect return 0; } /** * Run the networking functions for the PXO API */ void multi_pxo_api_process() { char *p; char msg_str[512]; Chat_command *cmd; pxo_channel *lookup; // give some time to psnet PSNET_TOP_LAYER_PROCESS(); // give some time to the game tracker API IdleGameTracker(); // give some time to the user tracker API PollPTrackNet(); // get any incoming text do { p = GetChatText(); if (p) { // process the chat line multi_pxo_chat_process_incoming(p); } } while(p); // get any incoming channel list stuff p = GetChannelList(); if (p) { multi_pxo_make_channels(p); } // process any chat commands cmd = GetChatCommand(); while (cmd) { switch (cmd->command) { case CC_USER_JOINING: // add a user, if he doesn't already exist if (multi_pxo_find_player(cmd->data) == NULL) multi_pxo_add_player(cmd->data); // increase the player count if (ON_CHANNEL() ) { lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name, Multi_pxo_channels); if (lookup != NULL) lookup->num_users++; } break; case CC_USER_LEAVING: // delete a user multi_pxo_del_player(cmd->data); // add a text message memset(msg_str, 0, 512); sprintf(msg_str, XSTR("*** %s has left", 950), cmd->data); multi_pxo_chat_process_incoming(msg_str); // decrease the player count if ( ON_CHANNEL() ) { lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels); if (lookup != NULL) lookup->num_users--; } break; case CC_DISCONNECTED: multi_pxo_handle_disconnect(); break; case CC_KICKED: multi_pxo_handle_kick(); break; case CC_NICKCHANGED: // process a nick change multi_pxo_process_nick_change(cmd->data); break; case CC_YOURCHANNEL: // copy the current channel info, and unset the switching status memset( &Multi_pxo_channel_current, 0, sizeof(pxo_channel) ); Multi_pxo_channel_switch.num_users = -1; SetNewChatChannel(NULL); strcpy_s(Multi_pxo_channel_current.name, cmd->data); // if we don't already have this guy on the list, add him lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name, Multi_pxo_channels); if (lookup == NULL) { // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail lookup = multi_pxo_add_channel(Multi_pxo_channel_current.name, &Multi_pxo_channels); } // set the user count to be 0 if (lookup != NULL) lookup->num_users = 0; // set our "last" channel to be this one strcpy_s(Multi_pxo_channel_last, Multi_pxo_channel_current.name); // refresh current channel server count multi_pxo_channel_refresh_current(); break; default: Int3(); } cmd = GetChatCommand(); } // handle any processing details if we're currently trying to join a channel multi_pxo_handle_channel_change(); } /** * Process a "nick" change event */ void multi_pxo_process_nick_change(char *data) { char *from, *to; player_list *lookup; // get the new string from = strtok(data," "); to = strtok(NULL,""); if((from != NULL) && (to != NULL)){ lookup = multi_pxo_find_player(from); if(lookup != NULL){ strcpy_s(lookup->name,to); // if this is also my nick, change it if(!stricmp(Multi_pxo_nick,from)){ strcpy_s(Multi_pxo_nick,to); } } } } /** * Autojoin an appropriate channel */ void multi_pxo_autojoin() { pxo_channel sw; memset( &sw, 0, sizeof(pxo_channel) ); sw.num_users = 0; strcpy_s(sw.name,MULTI_PXO_AUTOJOIN_CHANNEL); // if we found a valid room, attempt to join it multi_pxo_join_channel(&sw); } /** * Does the string match the "autojoin" prefix */ int multi_pxo_is_autojoin(char *name) { // check to see if the name is long enough if ( strlen(name) < strlen(MULTI_PXO_AUTOJOIN_PREFIX) ) return 0; // check to see if the first n chars match return !strnicmp(name, MULTI_PXO_AUTOJOIN_PREFIX, strlen(MULTI_PXO_AUTOJOIN_PREFIX)); } /** * Called from the game tracker API - server count update for a channel */ void multi_pxo_channel_count_update(char *name, int count) { pxo_channel *lookup; // lookup the channel name on the normal list lookup = multi_pxo_find_channel(name,Multi_pxo_channels); if (lookup != NULL) { lookup->num_servers = (ushort)count; nprintf(("Network","PXO : updated channel %s server count to %d\n",name,count)); ml_printf("PXO : updated channel %s server count to %d", name, count); } else { ml_printf("PXO : unable to locate channel when trying to update count for %s", name); } } // status bar stuff ----------------------------------------------- /** * Set the status text */ void multi_pxo_set_status_text(const char *txt) { // copy in the text memset(Multi_pxo_status_text, 0, MAX_PXO_TEXT_LEN); strncpy(Multi_pxo_status_text, txt, MAX_PXO_TEXT_LEN-1); // make sure it fits properly font::force_fit_string(Multi_pxo_status_text, MAX_PXO_TEXT_LEN-1, Multi_pxo_status_coords[gr_screen.res][2]); } /** * Blit the status text */ void multi_pxo_blit_status_text() { int w; // center and draw the text if(strlen(Multi_pxo_status_text)) { gr_set_color_fast(&Color_bright); gr_get_string_size(&w, NULL, Multi_pxo_status_text); 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, GR_RESIZE_MENU); } } // channel related stuff ------------------------------------------- /** * Get a list of channels on the server */ void multi_pxo_get_channels() { SendChatString(NOX("/list")); } /** * Clear the old channel list */ void multi_pxo_clear_channels() { pxo_channel *moveup,*backup; // only clear a non-null list if(Multi_pxo_channels != NULL){ // otherwise moveup = Multi_pxo_channels; backup = NULL; if(moveup != NULL){ do { backup = moveup; moveup = moveup->next; // free the struct itself vm_free(backup); backup = NULL; } while(moveup != Multi_pxo_channels); Multi_pxo_channels = NULL; } // head of the list of available channels Multi_pxo_channels = NULL; Multi_pxo_channel_count = 0; // item we're going to start displaying at Multi_pxo_channel_start = NULL; Multi_pxo_channel_start_index = -1; // items we've currently got selected Multi_pxo_channel_select = NULL; } } /** * Parse the input string and make a list of new channels */ void multi_pxo_make_channels(char *chan_str) { char *name_tok,*user_tok,*desc_tok; pxo_channel *res; pxo_channel *lookup; int num_users; nprintf(("Network","Making some channels!\n")); // set the last get time Multi_pxo_channel_last_refresh = f2fl(timer_get_fixed_seconds()); name_tok = strtok(chan_str," "); if(name_tok == NULL){ return; } name_tok += 1; do { // parse the user count token user_tok = strtok(NULL," "); // parse the channel description token desc_tok = strtok(NULL,"$"); // something invalid in the data, return here..... if((name_tok == NULL) || (user_tok == NULL) || (desc_tok == NULL)){ return; } // get the # of users num_users = (ubyte)atoi(user_tok); // if the # of users is > 0, or its not an autojoin, place it on the display list if((num_users > 0) || !multi_pxo_is_autojoin(name_tok)){ // see if it exists already, and if so, just update the user count lookup = multi_pxo_find_channel(name_tok,Multi_pxo_channels); if(lookup != NULL){ lookup->num_users = (short)num_users; } // add the channel else { res = multi_pxo_add_channel(name_tok,&Multi_pxo_channels); if(res != NULL){ res->num_users = (short)num_users; strcpy_s(res->desc,desc_tok); } } } // get the next name token name_tok = strtok(NULL," "); } while(name_tok != NULL); // refresh channels multi_pxo_set_status_text(XSTR("Connected to Parallax Online",951)); // if we haven't refreshed server counts yet, do it now if(Multi_pxo_channel_server_refresh < 0.0f){ multi_pxo_channel_refresh_servers(); } // if we don't already have this guy on the list, add him if(ON_CHANNEL()){ lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels); if(lookup == NULL){ // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels); } } } /** * Create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail */ pxo_channel *multi_pxo_add_channel(char *name,pxo_channel **list) { pxo_channel *new_channel; // try and allocate a new pxo_channel struct new_channel = (pxo_channel *)vm_malloc(sizeof(pxo_channel)); if ( new_channel == NULL ) { nprintf(("Network", "Cannot allocate space for new pxo_channel structure\n")); return NULL; } memset(new_channel,0,sizeof(pxo_channel)); // try and allocate a string for the channel name strncpy(new_channel->name,name,MAX_CHANNEL_NAME_LEN); // insert it on the list if ( *list != NULL ) { new_channel->next = (*list)->next; new_channel->next->prev = new_channel; (*list)->next = new_channel; new_channel->prev = *list; } else { *list = new_channel; (*list)->next = (*list)->prev = *list; } Multi_pxo_channel_count++; return new_channel; } /** * Lookup a channel with the specified name */ pxo_channel *multi_pxo_find_channel(char *name,pxo_channel *list) { pxo_channel *moveup; // look the sucker up moveup = list; if(moveup == NULL){ return NULL; } do { if(!stricmp(name,moveup->name)){ return moveup; } moveup = moveup->next; } while((moveup != list) && (moveup != NULL)); return NULL; } /** * Process the channel list (select, etc) */ void multi_pxo_process_channels() { int item_index,my; int idx; // if we don't have a start item, but the list is non-null if((Multi_pxo_channel_start == NULL) && (Multi_pxo_channels != NULL)){ Multi_pxo_channel_start = Multi_pxo_channels; Multi_pxo_channel_start_index = 0; } // if we don't have a selected item, but the list is non-null if((Multi_pxo_channel_select == NULL) && (Multi_pxo_channels != NULL)){ Multi_pxo_channel_select = Multi_pxo_channels; // set the text multi_pxo_set_status_text(Multi_pxo_channel_select->desc); } // if the "switch" delay timestamp is set, see if it has expired if((Multi_pxo_switch_delay != -1) && timestamp_elapsed(Multi_pxo_switch_delay)){ Multi_pxo_switch_delay = -1; } // see if we have a mouse click on the channel region if(Multi_pxo_channel_button.pressed()){ Multi_pxo_channel_button.get_mouse_pos(NULL,&my); // index from the top item_index = my / (gr_get_font_height() + 1); // select the item if possible if((item_index + Multi_pxo_channel_start_index) < Multi_pxo_channel_count){ Multi_pxo_channel_select = Multi_pxo_channel_start; for(idx=0;idxnext; } // set the text multi_pxo_set_status_text(Multi_pxo_channel_select->desc); } } // last refresh time if((Multi_pxo_channel_last_refresh > 0.0f) && ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_channel_last_refresh) > CHANNEL_REFRESH_TIME) ){ // refresh channels multi_pxo_set_status_text(XSTR("Refreshing Public Channel List",952)); // get a list of channels on the server (clear any old list as well) multi_pxo_get_channels(); // refresh Multi_pxo_channel_last_refresh = -1.0f; nprintf(("Network","Refreshing channels\n")); } // if we haven't updated our server channel counts in a while, do so again // last refresh time if((Multi_pxo_channel_server_refresh > 0.0f) && ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_channel_server_refresh) > CHANNEL_SERVER_REFRESH_TIME) ){ // do it _NOW_ I"M RIGHT HERE KILL ME WHAT ARE YOU WAITING FOR DO IT KILL ME DO IT NOW! multi_pxo_channel_refresh_servers(); } } /** * Send a request to refresh our channel server counts */ void multi_pxo_channel_refresh_servers() { pxo_channel *lookup; filter_game_list_struct filter; // traverse the list of existing channels we know about and query the game tracker about them lookup = Multi_pxo_channels; if (lookup == NULL) { return; } do { if ( strlen(lookup->name) ) { // copy in the info memset(&filter, 0, sizeof(filter_game_list_struct)); SDL_strlcpy(filter.channel, lookup->name, SDL_arraysize(filter.channel)); // send the request RequestGameCountWithFilter(&filter); } // next item lookup = lookup->next; } while ( (lookup != NULL) && (lookup != Multi_pxo_channels) ); // record the time Multi_pxo_channel_server_refresh = f2fl(timer_get_fixed_seconds()); } /** * Refresh current channel server count */ void multi_pxo_channel_refresh_current() { // send a request for a server count on this channel if ( strlen(Multi_pxo_channel_current.name) ) { // fill in the data filter_game_list_struct filter; memset(&filter, 0, sizeof(filter_game_list_struct)); SDL_strlcpy(filter.channel, Multi_pxo_channel_current.name, SDL_arraysize(filter.channel)); // send the request RequestGameCountWithFilter(&filter); } } /** * Display the channel list */ void multi_pxo_blit_channels() { pxo_channel *moveup; char chan_name[MAX_PXO_TEXT_LEN]; char chan_users[15]; char chan_servers[15]; int user_w,server_w; int disp_count,y_start; int line_height = gr_get_font_height() + 1; // blit as many channels as we can disp_count = 0; y_start = Multi_pxo_chan_coords[gr_screen.res][1]; moveup = Multi_pxo_channel_start; if(moveup == NULL){ return; } do { // if this is the currently selected item, highlight it if(moveup == Multi_pxo_channel_select){ gr_set_color_fast(&Color_bright); } // otherwise draw it normally else { gr_set_color_fast(&Color_normal); } // get the # of users on the channel memset(chan_users, 0, 15); sprintf(chan_users, "%d", moveup->num_users); // get the width of the user count string gr_get_string_size(&user_w, NULL, chan_users); // get the # of servers on the channel memset(chan_servers,0,15); sprintf(chan_servers, "%d", moveup->num_servers); // get the width of the user count string gr_get_string_size(&server_w, NULL, chan_servers); // make sure the name fits memset(chan_name, 0, MAX_PXO_TEXT_LEN); Assert(moveup->name); strcpy_s(chan_name,moveup->name); font::force_fit_string(chan_name, MAX_PXO_TEXT_LEN-1, Multi_pxo_chan_coords[gr_screen.res][2] - Multi_pxo_chan_column_offsets[gr_screen.res][CHAN_PLAYERS_COLUMN]); // blit the strings gr_string(Multi_pxo_chan_coords[gr_screen.res][0], y_start, chan_name + 1, GR_RESIZE_MENU); 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, GR_RESIZE_MENU); gr_set_color_fast(&Color_bright); 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, GR_RESIZE_MENU); // increment the displayed count disp_count++; y_start += line_height; // next item moveup = moveup->next; } while((moveup != Multi_pxo_channels) && (disp_count < Multi_pxo_max_chan_display[gr_screen.res])); } /** * Scroll channel list up */ void multi_pxo_scroll_channels_up() { // if we're already at the head of the list, do nothing if((Multi_pxo_channel_start == NULL) || (Multi_pxo_channel_start == Multi_pxo_channels)){ gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return; } // otherwise move up one Multi_pxo_channel_start = Multi_pxo_channel_start->prev; Multi_pxo_channel_start_index--; gamesnd_play_iface(InterfaceSounds::USER_SELECT); } /** * Scroll channel list down */ void multi_pxo_scroll_channels_down() { // if we're already at the tail of the list, do nothing if((Multi_pxo_channel_start == NULL) || (Multi_pxo_channel_start->next == Multi_pxo_channels)){ gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return; } // if we can't scroll further without going past the end of the viewable list, don't if((Multi_pxo_channel_start_index + Multi_pxo_max_chan_display[gr_screen.res]) >= Multi_pxo_channel_count){ gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return; } // otherwise move down one Multi_pxo_channel_start = Multi_pxo_channel_start->next; Multi_pxo_channel_start_index++; gamesnd_play_iface(InterfaceSounds::USER_SELECT); } /** * Attempt to join a channel */ void multi_pxo_join_channel(pxo_channel *chan) { char switch_msg[256]; // if we're already on this channel, do nothing if(ON_CHANNEL() && !stricmp(chan->name, Multi_pxo_channel_current.name)){ return; } // if we're already trying to join a channel, do nothing if(SWITCHING_CHANNELS()){ return; } // try and join the channel switch(SetNewChatChannel(chan->name)){ case -1 : Int3(); break; case 0 : // decrement the count of our current channel pxo_channel *lookup; lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels); if(lookup != NULL){ lookup->num_users--; } // set our current channel as none memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel)); Multi_pxo_channel_current.num_users = -1; multi_pxo_set_status_text(XSTR("Switching channels",953)); // copy the channel memcpy(&Multi_pxo_channel_switch,chan,sizeof(pxo_channel)); // clear the player list multi_pxo_clear_players(); // display a line of text indicating that we're switching channels memset(switch_msg,0,256); if(strlen(Multi_pxo_channel_switch.name) > 1){ sprintf(switch_msg, "[Switching to channel %s]", Multi_pxo_channel_switch.name + 1); } else { sprintf(switch_msg, "[Switching to channel %s]", Multi_pxo_channel_switch.name); } multi_pxo_chat_process_incoming(switch_msg, CHAT_MODE_CHANNEL_SWITCH); break; case 1 : Int3(); } } /** * Handle any processing details if we're currently trying to join a channel */ void multi_pxo_handle_channel_change() { // if we're not switching channels, do nothing if(!SWITCHING_CHANNELS()){ return; } // if we are, check the status switch(SetNewChatChannel(NULL)){ // failed to switch case -1 : // unset our switching struct memset(&Multi_pxo_channel_switch,0,sizeof(pxo_channel)); Multi_pxo_channel_switch.num_users = -1; // notify of error multi_pxo_set_status_text(XSTR("No channel (error while switching)",954)); break; // still switching case 0: break; // successfully changed case 1: // copy the current channel info, and unset the switching status memcpy(&Multi_pxo_channel_current,&Multi_pxo_channel_switch,sizeof(pxo_channel)); Multi_pxo_channel_switch.num_users = -1; // set our "last" channel strcpy_s(Multi_pxo_channel_last, Multi_pxo_channel_current.name); // notify the user multi_pxo_set_status_text(XSTR("Connected to Parallax Online",951)); // if we don't already have this guy on the list, add him pxo_channel *lookup; lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name,Multi_pxo_channels); if(lookup == NULL){ // create a new channel with the given name and place it on the channel list, return a pointer or NULL on fail lookup = multi_pxo_add_channel(Multi_pxo_channel_current.name,&Multi_pxo_channels); } // set the user count to be 1 (just me) if(lookup != NULL){ lookup->num_users = 1; } // set the "switch" delay timestamp Multi_pxo_switch_delay = timestamp(MULTI_PXO_SWITCH_DELAY_TIME); // refresh current channel server count multi_pxo_channel_refresh_current(); break; } } // player related stuff ------------------------------------------- /** * Clear the old player list */ void multi_pxo_clear_players() { player_list *moveup,*backup; // if the list is null, don't free it up if(Multi_pxo_players != NULL){ // otherwise moveup = Multi_pxo_players; backup = NULL; if(moveup != NULL){ do { backup = moveup; moveup = moveup->next; // free the struct itself vm_free(backup); backup = NULL; } while(moveup != Multi_pxo_players); Multi_pxo_players = NULL; } } Multi_pxo_player_start = NULL; Multi_pxo_player_select = NULL; } /** * Create a new player with the given name and place it on the player list, return a pointer or NULL on fail */ player_list *multi_pxo_add_player(char *name) { player_list *new_player; // try and allocate a new player_list struct new_player = (player_list *)vm_malloc(sizeof(player_list)); if ( new_player == NULL ) { nprintf(("Network", "Cannot allocate space for new player_list structure\n")); return NULL; } // try and allocate a string for the channel name strncpy(new_player->name, name, MAX_PLAYER_NAME_LEN); // insert it on the list if ( Multi_pxo_players != NULL ) { new_player->next = Multi_pxo_players->next; new_player->next->prev = new_player; Multi_pxo_players->next = new_player; new_player->prev = Multi_pxo_players; } else { Multi_pxo_players = new_player; Multi_pxo_players->next = Multi_pxo_players->prev = Multi_pxo_players; } // new player Multi_pxo_player_count++; return new_player; } /** * Remove a player with the given name */ void multi_pxo_del_player(char *name) { player_list *lookup; // try and find this guy lookup = Multi_pxo_players; if(lookup == NULL){ return; } do { // if we found a match, delete it if(!stricmp(name,lookup->name)){ // if this is the only item on the list, free stuff up if(lookup->next == lookup){ Assert(lookup == Multi_pxo_players); vm_free(lookup); Multi_pxo_players = NULL; multi_pxo_clear_players(); } // otherwise, just delete it else { lookup->next->prev = lookup->prev; lookup->prev->next = lookup->next; // if this was our selected item, unselect it if((lookup == Multi_pxo_player_select) && (Multi_pxo_player_select != NULL)){ Multi_pxo_player_select = Multi_pxo_player_select->next; } // if this was our point to start viewing from, select another if(lookup == Multi_pxo_player_start){ // if this is the head of the list, move up one if(Multi_pxo_players == lookup){ Multi_pxo_player_start = Multi_pxo_player_start->next; // Multi_pxo_player_start_index = 0; } // otherwise move back else { Multi_pxo_player_start = Multi_pxo_player_start->prev; } } // if this is the head of the list, move it up if(lookup == Multi_pxo_players){ Multi_pxo_players = Multi_pxo_players->next; } // free the item up lookup->next = NULL; lookup->prev = NULL; vm_free(lookup); } // new player Multi_pxo_player_count--; Assert(Multi_pxo_player_count >= 0); // we're done now return; } // next item lookup = lookup->next; } while((lookup != NULL) && (lookup != Multi_pxo_players)); } /** * Try and find a player with the given name, return a pointer to his entry (or NULL) */ player_list *multi_pxo_find_player(char *name) { player_list *lookup; // look through all players lookup = Multi_pxo_players; if(lookup == NULL){ return NULL; } do { if(!stricmp(name,lookup->name)){ return lookup; } lookup = lookup->next; } while((lookup != NULL) && (lookup != Multi_pxo_players)); // return NULL return NULL; } /** * Process the player list (select, etc) */ void multi_pxo_process_players() { int item_index,my; player_list *lookup; // if we don't have a start item, but the list is non-null if((Multi_pxo_player_start == NULL) && (Multi_pxo_players != NULL)){ Multi_pxo_player_start = Multi_pxo_players; } // if we don't have a selected item, but the list is non-null if((Multi_pxo_player_select == NULL) && (Multi_pxo_players != NULL)){ Multi_pxo_player_select = Multi_pxo_players; } // see if we have a mouse click on the channel region if(Multi_pxo_player_button.pressed()){ Multi_pxo_player_button.get_mouse_pos(NULL,&my); // index from the top item_index = my / (gr_get_font_height() + 1); // select the item if possible lookup = Multi_pxo_player_start; if(lookup == NULL){ return; } // select item 0 if(item_index == 0){ Multi_pxo_player_select = Multi_pxo_player_start; return; } do { // move to the next item lookup = lookup->next; item_index--; // if this item is our guy if((item_index == 0) && (lookup != Multi_pxo_players)){ Multi_pxo_player_select = lookup; return; } } while((lookup != Multi_pxo_players) && (item_index > 0)); } } /** * Display the player list */ void multi_pxo_blit_players() { player_list *moveup; char player_name[MAX_PXO_TEXT_LEN]; int disp_count,y_start; int line_height = gr_get_font_height() + 1; // blit as many channels as we can disp_count = 0; y_start = Multi_pxo_player_coords[gr_screen.res][1]; moveup = Multi_pxo_player_start; if(moveup == NULL){ return; } do { // if this is the currently selected item, highlight it if(moveup == Multi_pxo_player_select){ gr_set_color_fast(&Color_bright); } // otherwise draw it normally else { gr_set_color_fast(&Color_normal); } // make sure the string fits strcpy_s(player_name,moveup->name); font::force_fit_string(player_name, MAX_PXO_TEXT_LEN-1, Multi_pxo_player_coords[gr_screen.res][2]); // blit the string gr_string(Multi_pxo_player_coords[gr_screen.res][0], y_start, player_name, GR_RESIZE_MENU); // increment the displayed count disp_count++; y_start += line_height; // next item moveup = moveup->next; } while((moveup != Multi_pxo_players) && (disp_count < Multi_pxo_max_player_display[gr_screen.res])); } /** * Scroll player list up */ void multi_pxo_scroll_players_up() { // if we're already at the head of the list, do nothing if((Multi_pxo_player_start == NULL) || (Multi_pxo_player_start == Multi_pxo_players)){ gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return; } // otherwise move up one Multi_pxo_player_start = Multi_pxo_player_start->prev; gamesnd_play_iface(InterfaceSounds::USER_SELECT); } /** * Scroll player list down */ void multi_pxo_scroll_players_down() { player_list *lookup; int count = 0; // see if its okay to scroll down lookup = Multi_pxo_player_start; if(lookup == NULL ){ gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return; } count = 0; while(lookup->next != Multi_pxo_players){ lookup = lookup->next; count++; } // if we can move down if(count >= Multi_pxo_max_player_display[gr_screen.res]){ Multi_pxo_player_start = Multi_pxo_player_start->next; gamesnd_play_iface(InterfaceSounds::USER_SELECT); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } } // chat text stuff ----------------------------------------- /** * Initialize and create the chat text linked list */ void multi_pxo_chat_init() { int idx; chat_line *new_line; // no chat lines Multi_pxo_chat = NULL; Multi_pxo_chat_add = NULL; Multi_pxo_chat_start = NULL; Multi_pxo_chat_start_index = -1; // create the lines in a non-circular doubly linked list for(idx=0;idxprev = NULL; new_line->next = NULL; // insert it into the (empty) list if(Multi_pxo_chat == NULL){ Multi_pxo_chat = new_line; } // insert it onto the (non-empty) list else { Multi_pxo_chat->prev = new_line; new_line->next = Multi_pxo_chat; Multi_pxo_chat = new_line; } } // start adding chat lines at the beginning of the list Multi_pxo_chat_add = Multi_pxo_chat; } /** * Free up all chat list stuff */ void multi_pxo_chat_free() { chat_line *moveup, *backup; // free all items up moveup = Multi_pxo_chat; while(moveup != NULL){ backup = moveup; moveup = moveup->next; vm_free(backup); } // no chat lines Multi_pxo_chat = NULL; Multi_pxo_chat_add = NULL; Multi_pxo_chat_start = NULL; Multi_pxo_chat_start_index = -1; Multi_pxo_chat_count = 0; Multi_pxo_chat_slider.set_numberItems(0); } /** * Clear all lines of chat text in the chat area */ void multi_pxo_chat_clear() { chat_line *moveup; // clear the text in all the lines moveup = Multi_pxo_chat; while(moveup != NULL){ memset(moveup->text,0,MAX_CHAT_LINE_LEN+1); moveup = moveup->next; } // how many chat lines we have Multi_pxo_chat_count = 0; // start adding chat lines at the beginning of the list Multi_pxo_chat_add = Multi_pxo_chat; } /** * Add a line of text */ void multi_pxo_chat_add_line(const char *txt, int mode) { chat_line *temp; // copy in the text Assert(Multi_pxo_chat_add != NULL); strncpy(Multi_pxo_chat_add->text, txt, MAX_CHAT_LINE_LEN); Multi_pxo_chat_add->mode = mode; // if we're at the end of the list, move the front item down if(Multi_pxo_chat_add->next == NULL) { // store the new "head" of the list temp = Multi_pxo_chat->next; // move the current head to the end of the list Multi_pxo_chat_add->next = Multi_pxo_chat; temp->prev = NULL; Multi_pxo_chat->prev = Multi_pxo_chat_add; Multi_pxo_chat->next = NULL; // reset the head of the list Multi_pxo_chat = temp; // set the new add line Multi_pxo_chat_add = Multi_pxo_chat_add->next; memset(Multi_pxo_chat_add->text, 0, MAX_CHAT_LINE_LEN+1); Multi_pxo_chat_add->mode = CHAT_MODE_NORMAL; } // if we're not at the end of the list, just move up by one else { // set the new add line Multi_pxo_chat_add = Multi_pxo_chat_add->next; } // if we've reached max chat lines, don't increment if(Multi_pxo_chat_count < MAX_CHAT_LINES) { Multi_pxo_chat_count++; } // set the count 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 multi_pxo_goto_bottom(); } /** * Process an incoming line of text */ void multi_pxo_chat_process_incoming(const char *txt,int mode) { char msg_total[512],line[512]; int n_lines,idx; int n_chars[20]; const char *p_str[20]; // the initial line (unindented) const char *priv_ptr; // filter out "has left" channel messages, when switching channels if((SWITCHING_CHANNELS() || ((Multi_pxo_switch_delay != -1) && !timestamp_elapsed(Multi_pxo_switch_delay))) && multi_pxo_chat_is_left_message(txt)){ return; } // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL priv_ptr = multi_pxo_chat_is_private(txt); if(priv_ptr != NULL){ strcpy_s(msg_total, priv_ptr); } else { strcpy_s(msg_total, txt); } // determine what mode to display this text in // if this is private chat if(priv_ptr != NULL){ mode = CHAT_MODE_PRIVATE; } // all other chat else { // if this is a server message if(multi_pxo_is_server_text(txt)){ mode = CHAT_MODE_SERVER; } // if this is a MOTD else if(multi_pxo_is_motd_text(txt)){ multi_pxo_motd_add_text(txt); return; } // if this is the end of motd text else if(multi_pxo_is_end_of_motd_text(txt)){ multi_pxo_set_end_of_motd(); return; } } // split the text up into as many lines as necessary n_lines = split_str(msg_total, Multi_pxo_chat_coords[gr_screen.res][2] - 5, n_chars, p_str, 3); Assert((n_lines != -1) && (n_lines <= 20)); if((n_lines < 0) || (n_lines > 20)) { return; } // if the string fits on one line if(n_lines == 1) { multi_pxo_chat_add_line(msg_total,mode); } // if the string was split into multiple lines else { // add the first line memcpy(line,p_str[0],n_chars[0]); line[n_chars[0]] = '\0'; multi_pxo_chat_add_line(line,mode); // copy the rest of the lines for(idx=1; idx 1){ sprintf(title, XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name+1); // [[ on ]] } else { sprintf(title, XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name); // [[ on ]] } } else { strcpy_s(title,XSTR("Parallax Online - No Channel", 956)); } font::force_fit_string(title, MAX_PXO_TEXT_LEN-1, Multi_pxo_chat_coords[gr_screen.res][2] - 10); gr_get_string_size(&token_width,NULL,title); gr_set_color_fast(&Color_normal); 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, GR_RESIZE_MENU); // blit all active lines of text moveup = Multi_pxo_chat_start; disp_count = 0; y_start = Multi_pxo_chat_coords[gr_screen.res][1]; line_height = gr_get_font_height() + 1; while((moveup != NULL) && (moveup != Multi_pxo_chat_add) && (disp_count < (Multi_pxo_max_chat_display[gr_screen.res]))){ switch(moveup->mode){ // if this is text from the server, display it all "bright" case CHAT_MODE_SERVER: gr_set_color_fast(&Color_bright); gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text, GR_RESIZE_MENU); break; // if this is motd, display it all "bright" case CHAT_MODE_MOTD: gr_set_color_fast(&Color_bright_white); gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text, GR_RESIZE_MENU); break; // normal mode, just highlight the server case CHAT_MODE_PRIVATE: case CHAT_MODE_NORMAL: strcpy_s(piece,moveup->text); tok = strtok(piece," "); if(tok != NULL){ // get the width of just the first "piece" gr_get_string_size(&token_width, NULL, tok); // draw it brightly gr_set_color_fast(&Color_bright); gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, tok, GR_RESIZE_MENU); // draw the rest of the string normally tok = strtok(NULL,""); if(tok != NULL){ gr_set_color_fast(&Color_normal); gr_string(Multi_pxo_chat_coords[gr_screen.res][0] + token_width + 6, y_start, tok, GR_RESIZE_MENU); } } break; // carry mode, display with no highlight case CHAT_MODE_CARRY: gr_set_color_fast(&Color_normal); gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text, GR_RESIZE_MENU); break; // "switching channels mode", display it bright case CHAT_MODE_CHANNEL_SWITCH: gr_set_color_fast(&Color_bright); gr_string(Multi_pxo_chat_coords[gr_screen.res][0], y_start, moveup->text, GR_RESIZE_MENU); break; } // next chat line moveup = moveup->next; disp_count++; y_start += line_height; } if ((moveup != Multi_pxo_chat_add) && (moveup != NULL)) { Can_scroll_down = 1; } else { Can_scroll_down = 0; } } /** * Scroll to the very bottom of the chat area */ void multi_pxo_goto_bottom() { chat_line *backup; int idx; if (Multi_pxo_chat == NULL) { return; } // if we have less than the displayable amount of lines, do nothing if(Multi_pxo_chat_count <= Multi_pxo_max_chat_display[gr_screen.res]){ Multi_pxo_chat_start = Multi_pxo_chat; // nothing to do for the slider Multi_pxo_chat_slider.set_numberItems(0); return; } if (!Can_scroll_down) { // otherwise move back the right # of items backup = Multi_pxo_chat_add; for(idx=0; idxprev != NULL); backup = backup->prev; } Multi_pxo_chat_start = backup; // fixup the start index multi_pxo_chat_adjust_start(); } } /** * Scroll the text up */ void multi_pxo_scroll_chat_up() { // if we're already at the top of the list, don't do anything if ((Multi_pxo_chat_start == NULL) || (Multi_pxo_chat_start == Multi_pxo_chat)) { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); return; } // otherwise move up one Multi_pxo_chat_start = Multi_pxo_chat_start->prev; multi_pxo_chat_adjust_start(); gamesnd_play_iface(InterfaceSounds::USER_SELECT); } /** * Returns 1 if we can scroll down, 0 otherwise */ int multi_pxo_can_scroll_down() { chat_line *lookup; int count = 0; // see if its okay to scroll down lookup = Multi_pxo_chat_start; if (lookup == NULL) { return 0; } count = 0; while (lookup != Multi_pxo_chat_add) { lookup = lookup->next; count++; } // check if we can move down, return accordingly if (count > Multi_pxo_max_chat_display[gr_screen.res]) { return 1; } else { return 0; } } /** * Scroll the text down */ void multi_pxo_scroll_chat_down() { // if we can move down if (multi_pxo_can_scroll_down()) { Multi_pxo_chat_start = Multi_pxo_chat_start->next; multi_pxo_chat_adjust_start(); gamesnd_play_iface(InterfaceSounds::USER_SELECT); } else { gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL); } } /** * Process chat controls */ void multi_pxo_chat_process() { const char *remainder; const char *result; char msg[512]; int msg_pixel_width; // if the chat line is getting too long, fire off the message, putting the last // word on the next input line. memset(msg, 0, 512); Multi_pxo_chat_input.get_text(msg); remainder = ""; // determine if the width of the string in pixels is > than the inputbox width -- if so, // then send the message gr_get_string_size(&msg_pixel_width, NULL, msg); if ( msg_pixel_width >= (Multi_pxo_input_coords[gr_screen.res][2])) { auto last_space = strrchr(msg, ' '); if ( last_space ) { *last_space = '\0'; remainder = last_space + 1; } else { remainder = ""; } // if we're connected to a channel, send the chat to the server if(ON_CHANNEL()){ result = SendChatString(msg,1); if(result != NULL){ multi_pxo_chat_process_incoming(result); } // display any remainder of text on the next line Multi_pxo_chat_input.set_text(remainder); } else { Multi_pxo_chat_input.set_text(""); } } else if((Multi_pxo_chat_input.pressed() && (msg[0] != '\0')) || (strlen(msg) >= MAX_CHAT_LINE_LEN)) { // tack on the null terminator in the boundary case size_t x = strlen(msg); if(x >= MAX_CHAT_LINE_LEN){ msg[MAX_CHAT_LINE_LEN-1] = '\0'; } // ignore "/nick" commands if(multi_pxo_is_nick_command(msg)){ Multi_pxo_chat_input.set_text(""); return; } // send the chat to the server // if we're connected to a channel, send the chat to the server if(ON_CHANNEL()){ result = SendChatString(msg,1); if(result != NULL){ multi_pxo_chat_process_incoming(result); } // display any remainder of text on the next line Multi_pxo_chat_input.set_text(remainder); } else { Multi_pxo_chat_input.set_text(""); } } } // if the text is a private message, return a pointer to the beginning of the message, otherwise return NULL //XSTR:OFF // NOTE : DO NOT LOCALIZE THESE STRINGS!!!! THEY ARE CONSTANTS WHICH ARE CHECKED AGAINST // PXO CHAT SERVER DATA. THEY CANNOT CHANGE!!! #define PMSG_FROM "private message from " #define PMSG_TO "private message to " const char *multi_pxo_chat_is_private(const char *txt) { // quick check size_t from_len = strlen( PMSG_FROM ); if( strlen(txt) > from_len ){ // otherwise do a comparison if( !strnicmp(txt, PMSG_FROM, from_len) ){ return &txt[from_len]; } } // quick check size_t to_len = strlen( PMSG_TO ); if( strlen(txt) > to_len ){ // otherwise do a comparison if( !strnicmp(txt, PMSG_TO, to_len) ){ return &txt[to_len]; } } return NULL; } //XSTR:ON static const size_t pxo_prefix_len = strlen(MULTI_PXO_SERVER_PREFIX); /** * If the text came from the server */ int multi_pxo_is_server_text(const char *txt) { // if the message is prefaced by a *** if((strlen(txt) >= pxo_prefix_len) && !strncmp(txt, MULTI_PXO_SERVER_PREFIX, pxo_prefix_len)){ return 1; } return 0; } static const size_t motd_prefix_len = strlen(PXO_CHAT_MOTD_PREFIX); /** * If the text is message of the day text */ int multi_pxo_is_motd_text(const char *txt) { // if we're not on a channel, and this is not a channel switching message assume its coming from a server if((strlen(txt) >= motd_prefix_len) && !strncmp(txt, PXO_CHAT_MOTD_PREFIX, motd_prefix_len)){ return 1; } return 0; } static const size_t end_motd_prefix_len = strlen(PXO_CHAT_END_OF_MOTD_PREFIX); /** * If the text is the end of motd text */ int multi_pxo_is_end_of_motd_text(const char *txt) { // if we're not on a channel, and this is not a channel switching message assume its coming from a server if((strlen(txt) >= end_motd_prefix_len) && !strncmp(txt, PXO_CHAT_END_OF_MOTD_PREFIX, end_motd_prefix_len)){ return 1; } return 0; } /** * If the text is a "has left message" from the server */ int multi_pxo_chat_is_left_message(const char *txt) { // if the text is not server text if(!multi_pxo_is_server_text(txt)){ return 0; } // check to see if the last portion is the correct wording if((strlen(txt) > strlen(MULTI_PXO_HAS_LEFT)) && !strcmp(&txt[strlen(txt) - strlen(MULTI_PXO_HAS_LEFT)], MULTI_PXO_HAS_LEFT)){ return 1; } // check the end of the line return 0; } /** * Recalculate the chat start index, and adjust the slider properly */ void multi_pxo_chat_adjust_start() { chat_line *moveup; // if we have no chat if (Multi_pxo_chat == NULL) { Multi_pxo_chat_start_index = -1; return; } // traverse Multi_pxo_chat_start_index = 0; moveup = Multi_pxo_chat; while((moveup != Multi_pxo_chat_start) && (moveup != NULL)){ Multi_pxo_chat_start_index++; moveup = moveup->next; } // set the slider index Multi_pxo_chat_slider.force_currentItem(Multi_pxo_chat_start_index); } // motd stuff --------------------------------------------------------- /** * Initialize motd when going into this screen */ void multi_pxo_motd_init() { // zero the motd string strcpy_s(Pxo_motd, ""); // haven't gotten it yet Pxo_motd_end = 0; // haven't read it yet either Pxo_motd_read = 0; } /** * Set the motd text */ void multi_pxo_motd_add_text(const char *text) { size_t cur_len = strlen(Pxo_motd); size_t new_len; // sanity if(text == NULL){ return; } // make sure its motd text Assert(multi_pxo_is_motd_text(text)); if(!multi_pxo_is_motd_text(text)){ return; } // if its a 0 line motd if(strlen(text) <= motd_prefix_len){ return; } // add text to the motd new_len = strlen(text + motd_prefix_len) - 1; if((cur_len + new_len + 1) < MAX_PXO_MOTD_LEN){ strcat_s(Pxo_motd, text + motd_prefix_len + 1); strcat_s(Pxo_motd, "\n"); mprintf(("MOTD ADD : %s\n", Pxo_motd)); } } /** * Set end of motd */ void multi_pxo_set_end_of_motd() { int blink = 1; Pxo_motd_end = 1; mprintf(("MOTD ALL : %s\n", Pxo_motd)); Pxo_motd_read = 0; // do we have an old MOTD file laying around? If so, read it in and see if its the same uint old_chksum; uint new_chksum; // checksum the current motd new_chksum = cf_add_chksum_long(0, (ubyte*)Pxo_motd, strlen(Pxo_motd)); // checksum the old motd if its lying around CFILE *in = cfopen("oldmotd.txt", "rb"); if(in != NULL){ // read the old checksum cfread(&old_chksum, sizeof(old_chksum), 1, in); cfclose(in); // same checksum? no blink if(new_chksum == old_chksum){ blink = 0; } } // write out the motd for next time if(strlen(Pxo_motd)){ CFILE *out = cfopen("oldmotd.txt", "wb", CFILE_NORMAL, CF_TYPE_DATA); if(out != NULL){ // write all the text cfwrite(&new_chksum, sizeof(new_chksum), 1, out); // close the outfile cfclose(out); } } // set the blink stamp Pxo_motd_blink_stamp = -1; if(blink){ Pxo_motd_blink_on = 0; if(!Pxo_motd_blinked_already){ Pxo_motd_blink_stamp = timestamp(PXO_MOTD_BLINK_TIME); Pxo_motd_blink_on = 1; } } Pxo_motd_blinked_already = 1; } /** * Display the motd dialog */ void multi_pxo_motd_dialog() { // mark the motd as read Pxo_motd_read = 1; // simple popup, with a slider popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, Pxo_motd); } /** * Call to maybe blink the motd button */ void multi_pxo_motd_maybe_blit() { // if we got the end of the motd, and he hasn't read it yet if(Pxo_motd_end && !Pxo_motd_read && (Pxo_motd_blink_stamp != -1)){ // if the timestamp elapsed, flip the blink flag if(timestamp_elapsed(Pxo_motd_blink_stamp)){ Pxo_motd_blink_on = !Pxo_motd_blink_on; Pxo_motd_blink_stamp = timestamp(PXO_MOTD_BLINK_TIME); } // draw properly if(Pxo_motd_blink_on){ Multi_pxo_buttons[gr_screen.res][MULTI_PXO_MOTD].button.draw_forced(2); } } } // common dialog stuff ------------------------------------------------ int Multi_pxo_searching = 0; /** * Initialize the common dialog with the passed max input length */ void multi_pxo_com_init(int input_len) { int idx; // create the interface window Multi_pxo_com_window.create(0, 0, gr_screen.max_w_unscaled,gr_screen.max_h_unscaled, 0); Multi_pxo_com_window.set_mask_bmap(Multi_pxo_com_mask_fname[gr_screen.res]); // create the interface buttons for(idx=0; idx(ret_string) == -1) { return 1; } // user not a tracker pilot if ( !stricmp(ret_string,"-1") ) { return 1; } // otherwise parse into his id and callsign strcpy_s(temp_string, ret_string); tok = strtok(temp_string, " "); // get tracker id if (tok != NULL) { strcpy_s(Multi_pxo_retrieve_id, tok); // get the callsign tok = strtok(NULL, ""); if (tok != NULL) { strcpy_s(Multi_pxo_retrieve_name, tok); } // failure else { return 1; } } // failure of some kind or another else { return 1; } Multi_pxo_retrieve_mode = 1; return 0; } break; } // initial call to get his stats case 1: { // change the popup text popup_change_text(XSTR("Getting player stats",968)); // fill in the data memset(&Multi_pxo_pinfo, 0, sizeof(Multi_pxo_pinfo)); SDL_strlcpy(Multi_pxo_pinfo.pilot_name, Multi_pxo_retrieve_name, SDL_arraysize(Multi_pxo_pinfo.pilot_name)); Multi_pxo_pinfo.tracker_id = SDL_atoi(Multi_pxo_retrieve_id); // make the initial call to the API GetFSPilotData(reinterpret_cast(static_cast(0xffffffff)), nullptr, nullptr, 0); if (GetFSPilotData(&Multi_pxo_pinfo, Multi_pxo_retrieve_name, Multi_pxo_retrieve_id, 0) != 0){ return 2; } // if the call went through, set the mode to 2 else { Multi_pxo_retrieve_mode = 2; } break; } // busy retrieving his stats case 2: { switch (GetFSPilotData(nullptr, nullptr, nullptr, 0)) { // timeout, fail, cancel case -1: case 3: case 2: return 2; // got the data case 1: return 10; // still busy case 0: break; } break; } } // return not done yet return 0; } /** * Return 1 if Multi_pxo_pinfo was successfully filled in, 0 otherwise */ int multi_pxo_pinfo_get(char *name) { // run the popup Multi_pxo_retrieve_mode = 0; strcpy_s(Multi_pxo_retrieve_name, name); switch ( popup_till_condition(multi_pxo_pinfo_cond, XSTR("&Cancel", 779), XSTR("Retrieving player tracker id", 969)) ) { // success case 10 : return 1; // failed to get his tracker id case 1 : return 0; // failed to get his stats case 2 : return 0; } // we didn't get the stats return 0; } /** * Fire up the stats view popup */ void multi_pxo_pinfo_show() { // initialize the popup multi_pxo_pinfo_init(); // run the popup do { game_set_frametime(GS_STATE_PXO); } while ( !multi_pxo_pinfo_do() ); // close down the popup multi_pxo_pinfo_close(); } /** * Build the stats labels values */ void multi_pxo_pinfo_build_vals() { player *fs = &Multi_pxo_pinfo_player; // pilot name memset(Multi_pxo_pinfo_vals[0], 0, 50); strcpy_s(Multi_pxo_pinfo_vals[0], fs->callsign); font::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])); // rank memset(Multi_pxo_pinfo_vals[1], 0, 50); multi_sg_rank_build_name(Ranks[fs->stats.rank].name, Multi_pxo_pinfo_vals[1]); font::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])); // kills memset(Multi_pxo_pinfo_vals[2], 0, 50); sprintf(Multi_pxo_pinfo_vals[2], "%d", fs->stats.kill_count); // assists memset(Multi_pxo_pinfo_vals[3], 0, 50); sprintf(Multi_pxo_pinfo_vals[3], "%d", fs->stats.assists); // friendly kills memset(Multi_pxo_pinfo_vals[4], 0, 50); sprintf(Multi_pxo_pinfo_vals[4], "%d", fs->stats.kill_count - fs->stats.kill_count_ok); // missions flown memset(Multi_pxo_pinfo_vals[5], 0, 50); sprintf(Multi_pxo_pinfo_vals[5], "%d", (int)fs->stats.missions_flown); // flight time memset(Multi_pxo_pinfo_vals[6], 0, 50); game_format_time( fl2f((float)fs->stats.flight_time), Multi_pxo_pinfo_vals[6] ); // last flown memset(Multi_pxo_pinfo_vals[7], 0, 50); if (fs->stats.last_flown == 0) { strcpy_s(Multi_pxo_pinfo_vals[7], XSTR("No missions flown", 970) ); } else { time_t tmp_lf = fs->stats.last_flown; // don't cast a pointer to a type that can be either 32 or 64bit tm *tmr = gmtime( &tmp_lf ); if (tmr != NULL) strftime(Multi_pxo_pinfo_vals[7], 30, "%m/%d/%y %H:%M", tmr); else strcpy_s(Multi_pxo_pinfo_vals[7], ""); } // primary shots fired memset(Multi_pxo_pinfo_vals[8], 0, 50); sprintf(Multi_pxo_pinfo_vals[8], "%d", (int)fs->stats.p_shots_fired); // primary shots hit memset(Multi_pxo_pinfo_vals[9],0,50); sprintf(Multi_pxo_pinfo_vals[9], "%d", (int)fs->stats.p_shots_hit); // primary hit pct memset(Multi_pxo_pinfo_vals[10], 0, 50); if (fs->stats.p_shots_fired > 0) { sprintf(Multi_pxo_pinfo_vals[10], "%d%%", (int)((float)fs->stats.p_shots_hit / (float)fs->stats.p_shots_fired * 100.0f)); } else { strcpy_s(Multi_pxo_pinfo_vals[10], "0%"); } // secondary shots fired memset(Multi_pxo_pinfo_vals[11], 0, 50); sprintf(Multi_pxo_pinfo_vals[11], "%d", (int)fs->stats.s_shots_fired); // secondary shots hit memset(Multi_pxo_pinfo_vals[12], 0, 50); sprintf(Multi_pxo_pinfo_vals[12], "%d", (int)fs->stats.s_shots_hit); // secondary hit pct memset(Multi_pxo_pinfo_vals[13], 0, 50); if (fs->stats.s_shots_fired > 0) { sprintf(Multi_pxo_pinfo_vals[13], "%d%%", (int)((float)fs->stats.s_shots_hit / (float)fs->stats.s_shots_fired * 100.0f)); } else { strcpy_s(Multi_pxo_pinfo_vals[13], "0%"); } // primary friendly hits memset(Multi_pxo_pinfo_vals[14], 0, 50); sprintf(Multi_pxo_pinfo_vals[14], "%u", fs->stats.p_bonehead_hits); // primary friendly hit % memset(Multi_pxo_pinfo_vals[15], 0, 50); if (fs->stats.p_shots_fired > 0) { sprintf(Multi_pxo_pinfo_vals[15], "%d%%", (int)((float)100.0f*((float)fs->stats.p_bonehead_hits/(float)fs->stats.p_shots_fired))); } else { strcpy_s(Multi_pxo_pinfo_vals[15], "0%"); } // secondary friendly hits memset(Multi_pxo_pinfo_vals[16], 0, 50); sprintf(Multi_pxo_pinfo_vals[16], "%u", fs->stats.s_bonehead_hits); // secondary friendly hit % memset(Multi_pxo_pinfo_vals[17], 0, 50); if (fs->stats.s_shots_fired > 0) { sprintf(Multi_pxo_pinfo_vals[17], "%d%%", (int)((float)100.0f*((float)fs->stats.s_bonehead_hits/(float)fs->stats.s_shots_fired))); } else { strcpy_s(Multi_pxo_pinfo_vals[17], "0%"); } } /** * Initialize the popup */ void multi_pxo_pinfo_init() { int idx; // create the interface window Multi_pxo_pinfo_window.create(0,0,gr_screen.max_w_unscaled,gr_screen.max_h_unscaled,0); Multi_pxo_pinfo_window.set_mask_bmap(Multi_pxo_pinfo_mask_fname[gr_screen.res]); Multi_pxo_pinfo_bitmap = bm_load(Multi_pxo_pinfo_fname[gr_screen.res]); Assert(Multi_pxo_pinfo_bitmap != -1); // create the interface buttons for(idx=0; idxtext[cp->num_lines] = (char*)vm_malloc(Multi_pxo_chars_per_line[gr_screen.res]); if(cp->text[cp->num_lines] == NULL){ break; } // read in the next line cfgets(cp->text[cp->num_lines++], Multi_pxo_chars_per_line[gr_screen.res], in); // skip to the next page if necessary if(cp->num_lines == Multi_pxo_lines_pp[gr_screen.res]){ Multi_pxo_help_num_pages++; Assert(Multi_pxo_help_num_pages < MULTI_PXO_MAX_PAGES); if(Multi_pxo_help_num_pages >= MULTI_PXO_MAX_PAGES){ Multi_pxo_help_num_pages--; break; } cp = &Multi_pxo_help_pages[Multi_pxo_help_num_pages]; } } // close the file cfclose(in); } /** * Blit the current page */ void multi_pxo_help_blit_page() { int idx; int start_pos; int y_start, line_height; help_page *cp = &Multi_pxo_help_pages[Multi_pxo_help_cur]; // blit each line y_start = Multi_pxo_help_coords[gr_screen.res][1]; line_height = gr_get_font_height() + 1; for(idx=0;idxnum_lines;idx++){ // if the first symbol is "@", highlight the line if(cp->text[idx][0] == '@'){ gr_set_color_fast(&Color_bright); start_pos = 1; } else { gr_set_color_fast(&Color_normal); start_pos = 0; } // blit the line gr_string(Multi_pxo_help_coords[gr_screen.res][0], y_start, cp->text[idx] + start_pos, GR_RESIZE_MENU); // increment the y location y_start += line_height; } } /** * Process button presses */ void multi_pxo_help_process_buttons() { int idx; // process all buttons for(idx=0;idxIsFileError() ) { delete Multi_pxo_ban_get; Multi_pxo_ban_get = NULL; Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE; break; } // connecting, receiving if ( Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving() ) break; // done! if ( Multi_pxo_ban_get->IsFileReceived() ) { delete Multi_pxo_ban_get; Multi_pxo_ban_get = NULL; Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_STARTUP; } break; // start downloading files case PXO_BAN_MODE_IMAGES_STARTUP: // first thing - parse the banners file and pick a file multi_pxo_ban_parse_banner_file(0); // if we have no active file, we're done if ( (strlen(Multi_pxo_banner.ban_file) <= 0) || (strlen(Multi_pxo_banner.ban_file_url) <= 0) ) { Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE; break; } // if the file already exists, we're done if ( cf_exists(Multi_pxo_banner.ban_file, CF_TYPE_MULTI_CACHE) ) { Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE; break; } // otherwise try and download it cf_create_default_path_string(local_file, sizeof(local_file) - 1, CF_TYPE_MULTI_CACHE, Multi_pxo_banner.ban_file); // try creating the file get object Multi_pxo_ban_get = new InetGetFile(Multi_pxo_banner.ban_file_url, local_file, CF_TYPE_MULTI_CACHE); // bad if (Multi_pxo_ban_get == NULL) { Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE; break; } // go to the downloading images mode Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES; break; // downloading files case PXO_BAN_MODE_IMAGES: // error if ( Multi_pxo_ban_get->IsFileError() ) { delete Multi_pxo_ban_get; Multi_pxo_ban_get = NULL; Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE; break; } // connecting, receiving if ( Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving() ) break; // done! if ( Multi_pxo_ban_get->IsFileReceived() ) { delete Multi_pxo_ban_get; Multi_pxo_ban_get = NULL; Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE; } break; // done downloading - maybe load an image case PXO_BAN_MODE_IMAGES_DONE: // make sure we have a valid filename if (Multi_pxo_banner.ban_file[0] != '\0') Multi_pxo_banner.ban_bitmap = bm_load(Multi_pxo_banner.ban_file); // now we're idle Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE; break; // idle (done with EVERYTHING) case PXO_BAN_MODE_IDLE: // if the banner button was clicked if ( Multi_pxo_ban_button.pressed() ) { multi_pxo_ban_clicked(); } break; case PXO_BAN_MODE_CHOOSE_RANDOM: // first thing - parse the banners file and pick a file multi_pxo_ban_parse_banner_file(1); Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE; break; } } /** * Close */ void multi_pxo_ban_close() { // if we have a currently active transfer if(Multi_pxo_ban_get != NULL){ Multi_pxo_ban_get->AbortGet(); delete Multi_pxo_ban_get; Multi_pxo_ban_get = NULL; } // if we have a loaded bitmap, unload it if(Multi_pxo_banner.ban_bitmap != -1){ bm_unload(Multi_pxo_banner.ban_bitmap); Multi_pxo_banner.ban_bitmap = -1; } } /** * Parse the banners file and maybe fill in Multi_pxo_dl_file */ void multi_pxo_ban_parse_banner_file(int choose_existing) { char file_url[512] = ""; char banners[10][512]; char urls[10][512]; int exists[10]; int exist_count; int num_banners, idx; CFILE *in = cfopen(PXO_BANNERS_CONFIG_FILE, "rt", CFILE_NORMAL, CF_TYPE_MULTI_CACHE); Multi_pxo_banner.ban_bitmap = -1; strcpy_s(Multi_pxo_banner.ban_file, ""); strcpy_s(Multi_pxo_banner.ban_file_url, ""); strcpy_s(Multi_pxo_banner.ban_url, ""); // bad if(in == NULL){ return; } // clear all strings for(idx=0; idx<10; idx++){ strcpy_s(banners[idx], ""); strcpy_s(urls[idx], ""); } // get the global banner url if(cfgets(file_url, 254, in) == NULL){ cfclose(in); cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE); return; } drop_leading_white_space(file_url); drop_trailing_white_space(file_url); // verify that it's a proper url if ( strncmp(file_url, "http://", 7) && strncmp(file_url, "ftp://", 6) ) { cfclose(in); cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE); return; } // otherwise read in num_banners = 0; while(num_banners < 10){ // try and get the pcx if(cfgets(banners[num_banners], 254, in) == NULL){ break; } // try and get the url if(cfgets(urls[num_banners], 254, in) == NULL){ break; } // strip off trailing and leading whitespace drop_leading_white_space(banners[num_banners]); drop_trailing_white_space(banners[num_banners]); drop_leading_white_space(urls[num_banners]); drop_trailing_white_space(urls[num_banners]); // got one num_banners++; } // close the file cfclose(in); // no banners if(num_banners <= 0){ return; } // if we're only selecting files which already exist (previously downloaded) if(choose_existing){ // non exist for(idx=0; idx<10; idx++){ exists[idx] = 0; } // build a list of existing files exist_count = 0; for (idx = 0; idx < num_banners; idx++) { if ( cf_exists(banners[idx], CF_TYPE_MULTI_CACHE) ) { exists[idx] = 1; exist_count++; } } // bogus if(exist_count <= 0){ return; } // select one int select = (int)frand_range(0.0f, (float)exist_count); if(select >= exist_count){ select = exist_count - 1; } if(select < 0){ select = 0; } for(idx=0; idx= num_banners){ idx = num_banners - 1; } if(idx < 0){ idx = 0; } // base filename strcpy_s(Multi_pxo_banner.ban_file, banners[idx]); // get the full file url strcpy_s(Multi_pxo_banner.ban_file_url, file_url); strcat_s(Multi_pxo_banner.ban_file_url, banners[idx]); // url of where to go to when clicked strcpy_s(Multi_pxo_banner.ban_url, urls[idx]); } } /** * Any bitmap or info or whatever */ void multi_pxo_ban_draw() { // if we have a valid bitmap if(Multi_pxo_banner.ban_bitmap >= 0){ // if the mouse is over the banner button, highlight with a rectangle if(Multi_pxo_ban_button.is_mouse_on()){ gr_set_color_fast(&Color_bright_blue); 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, GR_RESIZE_MENU); } // draw the bitmap itself gr_set_bitmap(Multi_pxo_banner.ban_bitmap); gr_bitmap(Pxo_ban_coords[gr_screen.res][0], Pxo_ban_coords[gr_screen.res][1], GR_RESIZE_MENU); } } /** * Called when the URL button is clicked */ void multi_pxo_ban_clicked() { // if we have a valid bitmap and URL, launch the URL if((Multi_pxo_banner.ban_bitmap >= 0) && (Multi_pxo_banner.ban_url[0] != '\0')){ multi_pxo_url(Multi_pxo_banner.ban_url); } }