/* * 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 "palman/palman.h" #include "gamesnd/gamesnd.h" #include "gamesequence/gamesequence.h" #include "cfile/cfile.h" #include "network/chat_api.h" #include "popup/popup.h" #include "freespace2/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 "fs2netd/fs2netd_client.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); // load/set the palette void multi_pxo_load_palette(); // 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 } }; 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 } }; 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 ------------------------------------------------ char *Multi_pxo_com_fname[GR_NUM_RESOLUTIONS] = { "PXOPop", "2_PXOPop" }; 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 ----------------------------------------- char *Multi_pxo_pinfo_fname[GR_NUM_RESOLUTIONS] = { "PilotInfo2", "2_PilotInfo2" }; 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; 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 char *Multi_pxo_help_fname[GR_NUM_RESOLUTIONS] = { "PXHelp", "2_PXHelp" }; 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, gr_screen.max_h, 0); Multi_pxo_window.set_mask_bmap(Multi_pxo_mask_fname[gr_screen.res]); // multiplayer screen common palettes multi_pxo_load_palette(); // create the interface buttons for(idx=0;idx 0) { Multi_pxo_buttons[gr_screen.res][MULTI_PXO_RANKINGS].button.set_custom_cursor_bmap(Web_cursor_bitmap); } // create the channel list select button and hide it Multi_pxo_channel_button.create(&Multi_pxo_window, "", Multi_pxo_chan_coords[gr_screen.res][0], Multi_pxo_chan_coords[gr_screen.res][1], Multi_pxo_chan_coords[gr_screen.res][2], Multi_pxo_chan_coords[gr_screen.res][3], 0, 1); Multi_pxo_channel_button.hide(); // create the player list select button and hide it Multi_pxo_player_button.create(&Multi_pxo_window, "", Multi_pxo_player_coords[gr_screen.res][0], Multi_pxo_player_coords[gr_screen.res][1], Multi_pxo_player_coords[gr_screen.res][2], Multi_pxo_player_coords[gr_screen.res][3], 0, 1); Multi_pxo_player_button.hide(); // create the chat input box Multi_pxo_chat_input.create(&Multi_pxo_window, Multi_pxo_input_coords[gr_screen.res][0], Multi_pxo_input_coords[gr_screen.res][1], Multi_pxo_input_coords[gr_screen.res][2], MAX_CHAT_LINE_LEN + 1, "", UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_ESC_CLR | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_EAT_USED); Multi_pxo_chat_input.set_focus(); // create the banner button and hide it Multi_pxo_ban_button.create(&Multi_pxo_window, "", Pxo_ban_coords[gr_screen.res][0], Pxo_ban_coords[gr_screen.res][1], Pxo_ban_coords[gr_screen.res][2], Pxo_ban_coords[gr_screen.res][3], 0, 1); Multi_pxo_ban_button.hide(); // create the chat slider Multi_pxo_chat_slider.create(&Multi_pxo_window, Multi_pxo_chat_slider_coords[gr_screen.res][0], Multi_pxo_chat_slider_coords[gr_screen.res][1], Multi_pxo_chat_slider_coords[gr_screen.res][2], Multi_pxo_chat_slider_coords[gr_screen.res][3], 0, Multi_pxo_chat_slider_name[gr_screen.res], multi_pxo_scroll_chat_up, multi_pxo_scroll_chat_down, NULL); // set our connection status so that we do the right stuff next frame Multi_pxo_must_validate = 1; Multi_pxo_must_connect = 0; Multi_pxo_connected = 0; // channel we're currently connected to memset(&Multi_pxo_channel_current,0,sizeof(pxo_channel)); Multi_pxo_channel_current.num_users = -1; // channel we're currently trying to change to, or NULL if nont memset(&Multi_pxo_channel_switch,0,sizeof(pxo_channel)); Multi_pxo_channel_switch.num_users = -1; // last time clicked the url button (so we don't have repeats) Multi_pxo_ranking_last = -1.0f; // channel switching extra time delay stamp Multi_pxo_switch_delay = -1; // our nick for this session multi_pxo_underscore_nick(Player->callsign,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 k = Multi_pxo_window.process(); // process any keypresses switch (k) { case KEY_ESC: gamesnd_play_iface(SND_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) { // validate the current player with the master tracker (will create the pilot on the MT if necessary) bool validate_code = fs2netd_login(); if ( !validate_code ) { // go back to the main hall gameseq_post_event(GS_EVENT_MAIN_MENU); Multi_pxo_must_connect = 0; Multi_pxo_must_validate = 0; } // now we have to conenct 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) || (bmh != gr_screen.max_h)){ gr_clear(); } } else { gr_clear(); } } while(0); 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 } // load/set the palette void multi_pxo_load_palette() { // use the palette #ifndef HARDWARE_ONLY palette_use_bm_palette(Multi_pxo_palette); #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)){ // 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(SND_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(SND_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(SND_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(id_string, "%s %s", Multi_tracker_id_string, Player->callsign); // build the ip string memset(ip_string, 0, MAX_PXO_TEXT_LEN); sprintf(ip_string, "%s:%d", Multi_options_g.pxo_ip, PXO_CHAT_PORT); // 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 FS2NetD fs2netd_do_frame(); // 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 gr_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; // 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) ) { // send the request fs2netd_update_game_count(lookup->name); } // 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) ) { // send the request fs2netd_update_game_count(Multi_pxo_channel_current.name); } } /** * 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); gr_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(SND_GENERAL_FAIL); return; } // otherwise move up one Multi_pxo_channel_start = Multi_pxo_channel_start->prev; Multi_pxo_channel_start_index--; gamesnd_play_iface(SND_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(SND_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(SND_GENERAL_FAIL); return; } // otherwise move down one Multi_pxo_channel_start = Multi_pxo_channel_start->next; Multi_pxo_channel_start_index++; gamesnd_play_iface(SND_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); gr_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(SND_GENERAL_FAIL); return; } // otherwise move up one Multi_pxo_player_start = Multi_pxo_player_start->prev; gamesnd_play_iface(SND_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(SND_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(SND_USER_SELECT); } else { gamesnd_play_iface(SND_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)); } gr_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(SND_GENERAL_FAIL); return; } // otherwise move up one Multi_pxo_chat_start = Multi_pxo_chat_start->prev; multi_pxo_chat_adjust_start(); gamesnd_play_iface(SND_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(SND_USER_SELECT); } else { gamesnd_play_iface(SND_GENERAL_FAIL); } } /** * Process chat controls */ void multi_pxo_chat_process() { 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])) { remainder = strrchr(msg, ' '); if ( remainder ) { *remainder = '\0'; remainder++; } 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 int 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) { int cur_len = strlen(Pxo_motd); int 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,gr_screen.max_h, 0); Multi_pxo_com_window.set_mask_bmap(Multi_pxo_com_mask_fname[gr_screen.res]); // create the interface buttons for(idx=0; idxcallsign); gr_force_fit_string(Multi_pxo_pinfo_vals[0], 49, Multi_pxo_pinfo_coords[gr_screen.res][2] - (Multi_pxo_pinfo_val_x[gr_screen.res] - Multi_pxo_pinfo_coords[gr_screen.res][0])); // rank memset(Multi_pxo_pinfo_vals[1], 0, 50); multi_sg_rank_build_name(Ranks[fs->stats.rank].name, Multi_pxo_pinfo_vals[1]); gr_force_fit_string(Multi_pxo_pinfo_vals[1], 49, Multi_pxo_pinfo_coords[gr_screen.res][2] - (Multi_pxo_pinfo_val_x[gr_screen.res] - Multi_pxo_pinfo_coords[gr_screen.res][0])); // 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_hit > 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_hit > 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,gr_screen.max_h,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 = NULL; // 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); // 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 strncpy(Multi_pxo_banner.ban_file, banners[idx], MAX_FILENAME_LEN); // get the full file url strncpy(Multi_pxo_banner.ban_file_url, file_url, MULTI_OPTIONS_STRING_LEN); strncat(Multi_pxo_banner.ban_file_url, banners[idx], MULTI_OPTIONS_STRING_LEN); // url of where to go to when clicked strncpy(Multi_pxo_banner.ban_url, urls[idx], MULTI_OPTIONS_STRING_LEN); } } /** * 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); } }