mirror of
https://github.com/scp-fs2open/fs2open.github.com.git
synced 2026-11-07 08:15:59 -10:00
FSO doesn't appear to use this any longer. And even when it was enabled the requirements made it largely useless anyway. Removing to clean up cfile a bit and so we don't have to keep maintaining it.
5156 lines
134 KiB
C++
5156 lines
134 KiB
C++
/*
|
|
* 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 <winsock2.h>
|
|
#else
|
|
#include <sys/wait.h>
|
|
#endif
|
|
|
|
#include "network/multi_pxo.h"
|
|
#include "anim/animplay.h"
|
|
#include "ui/ui.h"
|
|
#include "io/key.h"
|
|
#include "bmpman/bmpman.h"
|
|
#include "gamesnd/gamesnd.h"
|
|
#include "gamesequence/gamesequence.h"
|
|
#include "cfile/cfile.h"
|
|
#include "network/chat_api.h"
|
|
#include "popup/popup.h"
|
|
#include "freespace.h"
|
|
#include "graphics/font.h"
|
|
#include "network/multi.h"
|
|
#include "network/multiui.h"
|
|
#include "network/multi_log.h"
|
|
#include "stats/medals.h"
|
|
#include "globalincs/alphacolors.h"
|
|
#include "graphics/2d.h"
|
|
#include "graphics/generic.h"
|
|
#include "io/timer.h"
|
|
#include "inetfile/inetgetfile.h"
|
|
#include "cfile/cfilesystem.h"
|
|
#include "options/OptionsManager.h"
|
|
#include "osapi/osregistry.h"
|
|
#include "parse/parselo.h"
|
|
#include "stats/scoring.h"
|
|
#include "playerman/player.h"
|
|
#include "network/multi_fstracker.h"
|
|
#include "network/ptrack.h"
|
|
#include "network/gtrack.h"
|
|
#include "menuui/mainhallmenu.h"
|
|
#include "debugconsole/console.h"
|
|
|
|
|
|
|
|
// ----------------------------------------------------------------------------------------------------
|
|
// PXO DEFINES/VARS
|
|
//
|
|
|
|
// 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();
|
|
|
|
// process a "nick" change event
|
|
void multi_pxo_process_nick_change(char *data);
|
|
|
|
// blit everything on the "normal" screen
|
|
void multi_pxo_blit_all();
|
|
|
|
// 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);
|
|
|
|
// 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();
|
|
|
|
// some convenient macros
|
|
#define SWITCHING_CHANNELS() (Multi_pxo_channel_switch.num_users != -1)
|
|
#define ON_CHANNEL() (Multi_pxo_channel_current.num_users != -1)
|
|
|
|
// 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;
|
|
|
|
// the list of available (displayed) channels
|
|
SCP_vector<pxo_channel> Multi_pxo_channels;
|
|
|
|
// item we're going to start displaying at
|
|
int Multi_pxo_channel_start = 0;
|
|
int Multi_pxo_channel_start_index = -1;
|
|
|
|
// items we've currently got selected
|
|
int Multi_pxo_channel_select = 0;
|
|
|
|
// 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);
|
|
|
|
// lookup a channel with the specified name
|
|
pxo_channel *multi_pxo_find_channel(char *name);
|
|
|
|
// process the channel list (select, etc)
|
|
void multi_pxo_process_channels(bool api_access);
|
|
|
|
// 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();
|
|
|
|
// channel list region
|
|
int Multi_pxo_player_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{ // GR_640
|
|
27, 121, 141, 261
|
|
},
|
|
{ // GR_1024
|
|
43, 194, 154, 417
|
|
},
|
|
};
|
|
|
|
int Multi_pxo_max_player_display[GR_NUM_RESOLUTIONS] = {
|
|
25, // GR_640
|
|
41 // GR_1024
|
|
};
|
|
UI_BUTTON Multi_pxo_player_button;
|
|
|
|
// slider coords
|
|
int Multi_pxo_player_slider_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{ // GR_640
|
|
1, 139, 21, 192
|
|
},
|
|
{ // GR_1024
|
|
2, 219, 33, 314
|
|
}
|
|
};
|
|
const char *Multi_pxo_player_slider_name[GR_NUM_RESOLUTIONS] = {
|
|
"slider", // GR_640
|
|
"2_slider" // GR_1024
|
|
};
|
|
|
|
// the list of players in the current channel
|
|
SCP_vector<SCP_string> Multi_pxo_players;
|
|
|
|
// item we're going to start displaying at
|
|
int Multi_pxo_player_start = 0;
|
|
int Multi_pxo_player_start_index = -1;
|
|
|
|
// items we've currently got selected
|
|
int Multi_pxo_player_select = -1;
|
|
|
|
// 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
|
|
void multi_pxo_add_player(const char *name);
|
|
|
|
// remove a player with the given name
|
|
void multi_pxo_del_player(const char *name);
|
|
|
|
// try and find a player with the given name, return a pointer to his entry (or NULL)
|
|
int multi_pxo_find_player(const char *name);
|
|
|
|
// process the player list (select, etc)
|
|
void multi_pxo_process_players(bool api_access);
|
|
|
|
// 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();
|
|
|
|
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 -----------------------------------------
|
|
|
|
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"
|
|
|
|
#define MULTI_PXO_CHAT_LINE_LEN 512
|
|
|
|
// the chat list
|
|
SCP_list<chat_line> Multi_pxo_chat;
|
|
|
|
// the current line to start displaying from
|
|
int Multi_pxo_chat_start = 0;
|
|
|
|
// input box for text
|
|
UI_INPUTBOX Multi_pxo_chat_input;
|
|
|
|
// slider for chat
|
|
UI_SLIDER2 Multi_pxo_chat_slider;
|
|
|
|
int Multi_pxo_chat_slider_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{ // GR_640
|
|
620, 206, 21, 147
|
|
},
|
|
{ // GR_1024
|
|
990, 295, 34, 269
|
|
}
|
|
};
|
|
|
|
const char *Multi_pxo_chat_slider_name[GR_NUM_RESOLUTIONS] = {
|
|
"slider",
|
|
"2_slider"
|
|
};
|
|
|
|
// extra delay time when switching channels
|
|
#define MULTI_PXO_SWITCH_DELAY_TIME 2000
|
|
static UI_TIMESTAMP Multi_pxo_switch_delay;
|
|
|
|
// 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
|
|
bool multi_pxo_can_scroll_down();
|
|
|
|
// 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;
|
|
static UI_TIMESTAMP Pxo_motd_blink_stamp;
|
|
int Pxo_motd_blink_on = 0;
|
|
int Pxo_motd_blinked_already = 0;
|
|
|
|
// initialize motd when going into this screen
|
|
void multi_pxo_motd_init();
|
|
|
|
// set the motd text
|
|
void multi_pxo_motd_add_text(const char *text);
|
|
|
|
// set end of motd
|
|
void multi_pxo_set_end_of_motd();
|
|
|
|
// display the motd dialog
|
|
void multi_pxo_motd_dialog();
|
|
|
|
// call to maybe blink the motd button
|
|
void multi_pxo_motd_maybe_blit();
|
|
|
|
|
|
// common dialog stuff ------------------------------------------------
|
|
const char *Multi_pxo_com_fname[GR_NUM_RESOLUTIONS] = {
|
|
"PXOPop",
|
|
"2_PXOPop"
|
|
};
|
|
const char *Multi_pxo_com_mask_fname[GR_NUM_RESOLUTIONS] = {
|
|
"PXOPop-m",
|
|
"2_PXOPop-m"
|
|
};
|
|
|
|
// popup coords
|
|
int Multi_pxo_com_coords[GR_NUM_RESOLUTIONS][2] = {
|
|
{ // GR_640
|
|
38, 129
|
|
},
|
|
{ // GR_1024
|
|
61, 207
|
|
}
|
|
};
|
|
|
|
// input box coords
|
|
int Multi_pxo_com_input_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{ // GR_640
|
|
53, 233, 448, 25
|
|
},
|
|
{ // GR_1024
|
|
85, 372, 716, 40
|
|
}
|
|
};
|
|
|
|
#define MULTI_PXO_COM_NUM_BUTTONS 2
|
|
#define MULTI_PXO_COM_CANCEL 0
|
|
#define MULTI_PXO_COM_OK 1
|
|
|
|
ui_button_info Multi_pxo_com_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_COM_NUM_BUTTONS] = {
|
|
{ // GR_640
|
|
ui_button_info("PXP_00", 573, 192, -1, -1, 0),
|
|
ui_button_info("PXP_01", 573, 226, -1, -1, 1)
|
|
},
|
|
{ // GR_1024
|
|
ui_button_info("2_PXP_00", 917, 308, -1, -1, 0),
|
|
ui_button_info("2_PXP_01", 917, 361, -1, -1, 1)
|
|
}
|
|
};
|
|
|
|
#define MULTI_PXO_COM_NUM_TEXT 2
|
|
UI_XSTR Multi_pxo_com_text[GR_NUM_RESOLUTIONS][MULTI_PXO_COM_NUM_TEXT] = {
|
|
{ // GR_640
|
|
{ "&Cancel", 645, 510, 204, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_com_buttons[0][MULTI_PXO_COM_CANCEL].button },
|
|
{ "&Ok", 669, 548, 233, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_com_buttons[0][MULTI_PXO_COM_OK].button }
|
|
},
|
|
{ // GR_1024
|
|
{ "&Cancel", 645, 847, 327, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_com_buttons[1][MULTI_PXO_COM_CANCEL].button },
|
|
{ "&Ok", 669, 877, 372, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_com_buttons[1][MULTI_PXO_COM_OK].button }
|
|
}
|
|
};
|
|
|
|
int Multi_pxo_com_bitmap = -1;
|
|
UI_WINDOW Multi_pxo_com_window;
|
|
UI_INPUTBOX Multi_pxo_com_input;
|
|
|
|
// text on the "top" half of the dialog display area
|
|
char Multi_pxo_com_top_text[MAX_PXO_TEXT_LEN];
|
|
|
|
// text on the "middle" portion of the dialog display area
|
|
char Multi_pxo_com_middle_text[MAX_PXO_TEXT_LEN];
|
|
|
|
// text on the "bottom" half of the dialog display area
|
|
char Multi_pxo_com_bottom_text[MAX_PXO_TEXT_LEN];
|
|
|
|
int Multi_pxo_com_top_text_coords[GR_NUM_RESOLUTIONS][2] = {
|
|
{ // GR_640
|
|
58, 152
|
|
},
|
|
{ // GR_1024
|
|
91, 227
|
|
}
|
|
};
|
|
int Multi_pxo_com_middle_text_y[GR_NUM_RESOLUTIONS] = {
|
|
172, // GR_640
|
|
280 // GR_1024
|
|
};
|
|
int Multi_pxo_com_bottom_text_y[GR_NUM_RESOLUTIONS] = {
|
|
192, // GR_640
|
|
326 // GR_1024
|
|
};
|
|
|
|
// initialize the common dialog with the passed max input length
|
|
void multi_pxo_com_init();
|
|
|
|
// close down the common dialog
|
|
void multi_pxo_com_close();
|
|
|
|
// blit all text lines, top, middle, bottoms
|
|
void multi_pxo_com_blit_text();
|
|
|
|
// set the top text, shortening as necessary
|
|
void multi_pxo_com_set_top_text(const char *txt);
|
|
|
|
// set the middle text, shortening as necessary
|
|
void multi_pxo_com_set_middle_text(const char *txt);
|
|
|
|
// set the bottom text, shortening as necessary
|
|
void multi_pxo_com_set_bottom_text(const char *txt);
|
|
|
|
|
|
// private channel join stuff -----------------------------------------
|
|
#define MULTI_PXO_PRIV_MAX_TEXT_LEN 30
|
|
|
|
// max private channel name length
|
|
char Multi_pxo_priv_chan[MULTI_PXO_PRIV_MAX_TEXT_LEN+100];
|
|
|
|
// return code, set to something other than -1 if we're supposed to return
|
|
int Multi_pxo_priv_return_code = -1;
|
|
|
|
// initialize the popup
|
|
void multi_pxo_priv_init();
|
|
|
|
// close down the popup
|
|
void multi_pxo_priv_close();
|
|
|
|
// run the popup, 0 if still running, -1 if cancel, 1 if ok
|
|
int multi_pxo_priv_popup();
|
|
|
|
// process button presses
|
|
void multi_pxo_priv_process_buttons();
|
|
|
|
// handle a button press
|
|
void multi_pxo_priv_button_pressed(int n);
|
|
|
|
// process the inputbox
|
|
void multi_pxo_priv_process_input();
|
|
|
|
|
|
// find player stuff -----------------------------------------
|
|
|
|
char Multi_pxo_find_channel[MAX_CHANNEL_NAME_LEN+1];
|
|
|
|
// return code, set to something other than -1 if we're supposed to return
|
|
int Multi_pxo_find_return_code = -1;
|
|
|
|
// initialize the popup
|
|
void multi_pxo_find_init();
|
|
|
|
// close down the popup
|
|
void multi_pxo_find_close();
|
|
|
|
// run the popup, 0 if still running, -1 if cancel, 1 if ok
|
|
int multi_pxo_find_popup();
|
|
|
|
// process button presses
|
|
void multi_pxo_find_process_buttons();
|
|
|
|
// handle a button press
|
|
void multi_pxo_find_button_pressed(int n);
|
|
|
|
// process the inputbox
|
|
void multi_pxo_find_process_input();
|
|
|
|
// process search mode if applicable
|
|
void multi_pxo_find_search_process();
|
|
|
|
|
|
// player info stuff -----------------------------------------
|
|
const char *Multi_pxo_pinfo_fname[GR_NUM_RESOLUTIONS] = {
|
|
"PilotInfo2",
|
|
"2_PilotInfo2"
|
|
};
|
|
const char *Multi_pxo_pinfo_mask_fname[GR_NUM_RESOLUTIONS] = {
|
|
"PilotInfo2-M",
|
|
"2_PilotInfo2-M"
|
|
};
|
|
|
|
// medals
|
|
#define MULTI_PXO_PINFO_NUM_BUTTONS 2
|
|
#define MULTI_PXO_PINFO_MEDALS 0
|
|
#define MULTI_PXO_PINFO_OK 1
|
|
|
|
ui_button_info Multi_pxo_pinfo_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_PINFO_NUM_BUTTONS] = {
|
|
{ // GR_640
|
|
ui_button_info("PI2_00", 328, 446, 319, 433, 0),
|
|
ui_button_info("PI2_01", 376, 446, 382, 433, 1),
|
|
},
|
|
{ // GR_1024
|
|
ui_button_info("2_PI2_00", 525, 714, 510, 695, 0),
|
|
ui_button_info("2_PI2_01", 601, 714, 611, 695, 1),
|
|
}
|
|
};
|
|
|
|
// text
|
|
#define MULTI_PXO_PINFO_NUM_TEXT 2
|
|
UI_XSTR Multi_pxo_pinfo_text[GR_NUM_RESOLUTIONS][MULTI_PXO_PINFO_NUM_TEXT] = {
|
|
{ // GR_640
|
|
{ "Medals", 1037, 319, 433, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_pinfo_buttons[0][MULTI_PXO_PINFO_MEDALS].button },
|
|
{ "Ok", 345, 382, 433, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_pinfo_buttons[0][MULTI_PXO_PINFO_OK].button },
|
|
},
|
|
{ // GR_1024
|
|
{ "Medals", 1037, 510, 695, UI_XSTR_COLOR_GREEN, -1, &Multi_pxo_pinfo_buttons[1][MULTI_PXO_PINFO_MEDALS].button },
|
|
{ "Ok", 345, 611, 695, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_pinfo_buttons[1][MULTI_PXO_PINFO_OK].button },
|
|
}
|
|
};
|
|
|
|
int Multi_pxo_pinfo_bitmap = -1;
|
|
UI_WINDOW Multi_pxo_pinfo_window;
|
|
|
|
vmt_stats_struct Multi_pxo_pinfo;
|
|
player Multi_pxo_pinfo_player;
|
|
|
|
int Multi_pxo_retrieve_mode = -1;
|
|
|
|
char Multi_pxo_retrieve_name[MAX_PLAYER_NAME_LEN+1];
|
|
char Multi_pxo_retrieve_id[128];
|
|
|
|
// stats label stuff
|
|
#define MULTI_PXO_PINFO_NUM_LABELS 18
|
|
|
|
int Multi_pxo_pinfo_coords[GR_NUM_RESOLUTIONS][4] = {
|
|
{ // GR_640
|
|
37, 142, 377, 289
|
|
},
|
|
{ // GR_640
|
|
54, 227, 602, 462
|
|
},
|
|
};
|
|
int Multi_pxo_pinfo_val_x[GR_NUM_RESOLUTIONS] = {
|
|
230, // GR_640
|
|
310 // GR_1024
|
|
};
|
|
|
|
char *Multi_pxo_pinfo_stats_labels[MULTI_PXO_PINFO_NUM_LABELS];
|
|
|
|
char Multi_pxo_pinfo_vals[MULTI_PXO_PINFO_NUM_LABELS][50];
|
|
|
|
int Multi_pxo_pinfo_stats_spacing[MULTI_PXO_PINFO_NUM_LABELS] = {
|
|
10,20,10,10,20,10,10,20,10,10,20,10,10,20,10,20,10,0
|
|
};
|
|
|
|
// popup conditional functions, returns 10 on successful get of stats
|
|
int multi_pxo_pinfo_cond();
|
|
|
|
// return 1 if Multi_pxo_pinfo was successfully filled in, 0 otherwise
|
|
int multi_pxo_pinfo_get(const 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];
|
|
UI_TIMESTAMP Multi_pxo_notify_stamp;
|
|
|
|
// add a notification string
|
|
void multi_pxo_notify_add(const char *txt);
|
|
|
|
// blit and process the notification string
|
|
void multi_pxo_notify_blit();
|
|
|
|
|
|
// help screen stuff -----------------------------------------
|
|
//XSTR:OFF
|
|
const char *Multi_pxo_help_fname[GR_NUM_RESOLUTIONS] = {
|
|
"PXHelp",
|
|
"2_PXHelp"
|
|
};
|
|
const char *Multi_pxo_help_mask_fname[GR_NUM_RESOLUTIONS] = {
|
|
"PXOHelp-M",
|
|
"2_PXOHelp-M"
|
|
};
|
|
|
|
#define MULTI_PXO_HELP_NUM_BUTTONS 3
|
|
#define MULTI_PXO_HELP_PREV 0
|
|
#define MULTI_PXO_HELP_NEXT 1
|
|
#define MULTI_PXO_HELP_CONTINUE 2
|
|
|
|
ui_button_info Multi_pxo_help_buttons[GR_NUM_RESOLUTIONS][MULTI_PXO_HELP_NUM_BUTTONS] = {
|
|
{ // GR_640
|
|
ui_button_info("PXH_00", 15, 389, -1, -1, 0),
|
|
ui_button_info("PXH_01", 60, 389, -1, -1, 1),
|
|
ui_button_info("PXH_02", 574, 431, 571, 413, 2),
|
|
},
|
|
{ // GR_1024
|
|
ui_button_info("2_PXH_00", 24, 622, -1, -1, 0),
|
|
ui_button_info("2_PXH_01", 96, 622, -1, -1, 1),
|
|
ui_button_info("2_PXH_02", 919, 689, 928, 663, 2),
|
|
}
|
|
};
|
|
|
|
#define MULTI_PXO_HELP_NUM_TEXT 1
|
|
UI_XSTR Multi_pxo_help_text[GR_NUM_RESOLUTIONS][MULTI_PXO_HELP_NUM_TEXT] = {
|
|
{ // GR_640
|
|
{"Continue", 1069, 571, 413, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_help_buttons[0][MULTI_PXO_HELP_CONTINUE].button },
|
|
},
|
|
{ // GR_1024
|
|
{"Continue", 1069, 928, 663, UI_XSTR_COLOR_PINK, -1, &Multi_pxo_help_buttons[1][MULTI_PXO_HELP_CONTINUE].button },
|
|
},
|
|
};
|
|
|
|
// help text
|
|
#define MULTI_PXO_HELP_FILE "pxohelp.txt"
|
|
|
|
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_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;
|
|
|
|
// 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;
|
|
|
|
#define PXO_BANNER_SWAP_TIME 120000
|
|
static UI_TIMESTAMP Multi_pxo_ban_swap_stamp;
|
|
|
|
// 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;
|
|
|
|
// 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(bool api_access);
|
|
|
|
// 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();
|
|
|
|
// 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, bool api_access)
|
|
{
|
|
if (!api_access) {
|
|
// load the background bitmap
|
|
Multi_pxo_bitmap = bm_load(Multi_pxo_bitmap_fname[gr_screen.res]);
|
|
if (Multi_pxo_bitmap < 0) {
|
|
// we failed to load the bitmap - this is very bad
|
|
Int3();
|
|
}
|
|
|
|
// load up the private channel bitmap
|
|
Multi_pxo_com_bitmap = bm_load(Multi_pxo_com_fname[gr_screen.res]);
|
|
Assert(Multi_pxo_com_bitmap != -1);
|
|
|
|
// create the interface window
|
|
Multi_pxo_window.create(0, 0, gr_screen.max_w_unscaled, gr_screen.max_h_unscaled, 0);
|
|
Multi_pxo_window.set_mask_bmap(Multi_pxo_mask_fname[gr_screen.res]);
|
|
|
|
// create the interface buttons
|
|
for (int idx = 0; idx < MULTI_PXO_NUM_BUTTONS; idx++) {
|
|
// create the object
|
|
Multi_pxo_buttons[gr_screen.res][idx].button.create(&Multi_pxo_window,
|
|
"",
|
|
Multi_pxo_buttons[gr_screen.res][idx].x,
|
|
Multi_pxo_buttons[gr_screen.res][idx].y,
|
|
1,
|
|
1,
|
|
0,
|
|
1);
|
|
|
|
// set the sound to play when highlighted
|
|
Multi_pxo_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
|
|
|
|
// set the ani for the button
|
|
Multi_pxo_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_buttons[gr_screen.res][idx].filename);
|
|
|
|
// set the hotspot
|
|
Multi_pxo_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_buttons[gr_screen.res][idx].hotspot);
|
|
}
|
|
|
|
// add all xstrs
|
|
for (int idx = 0; idx < MULTI_PXO_NUM_TEXT; idx++) {
|
|
Multi_pxo_window.add_XSTR(&Multi_pxo_text[gr_screen.res][idx]);
|
|
}
|
|
|
|
// make all scrolling buttons repeatable
|
|
Multi_pxo_buttons[gr_screen.res][MULTI_PXO_TEXT_UP].button.repeatable(1);
|
|
Multi_pxo_buttons[gr_screen.res][MULTI_PXO_TEXT_DOWN].button.repeatable(1);
|
|
Multi_pxo_buttons[gr_screen.res][MULTI_PXO_CHAN_UP].button.repeatable(1);
|
|
Multi_pxo_buttons[gr_screen.res][MULTI_PXO_CHAN_DOWN].button.repeatable(1);
|
|
Multi_pxo_buttons[gr_screen.res][MULTI_PXO_PLIST_UP].button.repeatable(1);
|
|
Multi_pxo_buttons[gr_screen.res][MULTI_PXO_PLIST_DOWN].button.repeatable(1);
|
|
|
|
// set the mouseover cursor if it loaded ok
|
|
if (Web_cursor != NULL) {
|
|
Multi_pxo_buttons[gr_screen.res][MULTI_PXO_RANKINGS].button.set_custom_cursor(Web_cursor);
|
|
}
|
|
|
|
// 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);
|
|
}
|
|
|
|
if (use_last_channel && strlen(Multi_pxo_channel_last)) {
|
|
Multi_pxo_use_last_channel = 1;
|
|
} else {
|
|
memset(Multi_pxo_channel_last, 0, MAX_CHANNEL_NAME_LEN + 1);
|
|
Multi_pxo_use_last_channel = 0;
|
|
}
|
|
|
|
// 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 = UI_TIMESTAMP::invalid();
|
|
|
|
// 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_clear();
|
|
|
|
// initialize http
|
|
multi_pxo_ban_init();
|
|
|
|
if (!api_access) {
|
|
// 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);
|
|
|
|
if (!api_access) {
|
|
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(bool api_access)
|
|
{
|
|
if (!api_access) {
|
|
// 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;
|
|
|
|
if (!api_access) {
|
|
// 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_clear();
|
|
|
|
// close http stuff
|
|
multi_pxo_ban_close();
|
|
}
|
|
|
|
// run normally (no popups)
|
|
void multi_pxo_do_normal(bool api_access)
|
|
{
|
|
int validate_code;
|
|
|
|
if (!api_access) {
|
|
int k = Multi_pxo_window.process();
|
|
|
|
// process any keypresses
|
|
switch (k) {
|
|
case KEY_ESC:
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
break;
|
|
}
|
|
|
|
// check for button presses
|
|
multi_pxo_check_buttons();
|
|
|
|
// if we're not in a chatroom, disable and hide the chat input box
|
|
if (!ON_CHANNEL()) {
|
|
Multi_pxo_chat_input.hide();
|
|
Multi_pxo_chat_input.disable();
|
|
} else {
|
|
Multi_pxo_chat_input.enable();
|
|
Multi_pxo_chat_input.unhide();
|
|
}
|
|
|
|
// blit everything
|
|
multi_pxo_blit_all();
|
|
|
|
// flip the page
|
|
gr_flip();
|
|
}
|
|
|
|
// if we need to get tracker info for ourselves, do so
|
|
if (Multi_pxo_must_validate) {
|
|
// initialize the master tracker API for Freespace
|
|
multi_fs_tracker_init();
|
|
|
|
// validate game data first, for initial game/mod ident
|
|
if (multi_fs_tracker_validate_game_data() < 0) {
|
|
// in the event of a connection failure we should just dump back to the mainhall
|
|
popup(PF_USE_AFFIRMATIVE_ICON | PF_TITLE_BIG | PF_TITLE_RED, 1, POPUP_OK, XSTR("Failed to connect to Parallax Online!", 947));
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
|
|
return;
|
|
}
|
|
|
|
// validate the current player with the master tracker (will create the pilot on the MT if necessary)
|
|
validate_code = multi_fs_tracker_validate(0);
|
|
|
|
if (validate_code != 1) {
|
|
// show an error popup if it failed (not cancelled by the user)
|
|
if (validate_code == 0) {
|
|
switch (popup(PF_USE_AFFIRMATIVE_ICON | PF_WEB_CURSOR_1 | PF_WEB_CURSOR_2, 3, POPUP_CANCEL,XSTR("&Create Acct",936), XSTR("&Verify Acct",937), XSTR("PXO Login not accepted. You may visit the Parallax Online website to create or verify your login. Or you may click Cancel to play without using the Parallax Online service. (You may switch back to Parallax Online from the Options Menu under the Multi tab.)",938))) {
|
|
case 0:
|
|
nprintf(("Network","PXO CANCEL\n"));
|
|
|
|
// flip player "pxo" off and push to the join game screen,
|
|
// they will have to follow the instructions above to toggle PXO back on
|
|
// and since player is accepting, we need to persist changes
|
|
Multi_options_g.pxo = false;
|
|
options::OptionsManager::instance()->set_ingame_binary_option("Multi.TogglePXO", false);
|
|
if (Using_in_game_options) {
|
|
options::OptionsManager::instance()->persistChanges();
|
|
}
|
|
// Net_game_tcp_mode = NET_TCP;
|
|
gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME);
|
|
break;
|
|
|
|
case 1:
|
|
nprintf(("Network","PXO CREATE\n"));
|
|
// fire up the given URL
|
|
multi_pxo_url(Multi_options_g.pxo_create_url);
|
|
break;
|
|
|
|
case 2:
|
|
nprintf(("Network","PXO VERIFY\n"));
|
|
// fire up the given URL
|
|
multi_pxo_url(Multi_options_g.pxo_verify_url);
|
|
break;
|
|
}
|
|
}
|
|
|
|
Multi_pxo_must_connect = 0;
|
|
Multi_pxo_must_validate = 0;
|
|
}
|
|
// now we have to connect to PXO
|
|
else {
|
|
Multi_pxo_must_connect = 1;
|
|
Multi_pxo_must_validate = 0;
|
|
}
|
|
}
|
|
|
|
// if we need to connect, do so now
|
|
if (Multi_pxo_must_connect) {
|
|
SCP_string pxo_ident = "PXO: Game identified as " + Multi_fs_tracker_game_name;
|
|
multi_pxo_chat_process_incoming(pxo_ident.c_str(), CHAT_MODE_CARRY);
|
|
|
|
// for now, just try once
|
|
Multi_pxo_connected = multi_pxo_connect();
|
|
|
|
// if we successfully connected, send a request for a list of channels on the server
|
|
if (Multi_pxo_connected) {
|
|
multi_pxo_get_channels();
|
|
|
|
// set our status
|
|
multi_pxo_set_status_text(XSTR("Retrieving Public Channels",939));
|
|
} else {
|
|
// set our status
|
|
multi_pxo_set_status_text(XSTR("Failed to connect to Parallax Online",940));
|
|
}
|
|
|
|
// no longer need to connect
|
|
Multi_pxo_must_connect = 0;
|
|
}
|
|
}
|
|
|
|
// blit everything on the "normal" screen
|
|
void multi_pxo_blit_all()
|
|
{
|
|
// draw the background, etc
|
|
gr_reset_clip();
|
|
// GR_MAYBE_CLEAR_RES(Multi_pxo_bitmap);
|
|
int bmap = Multi_pxo_bitmap;
|
|
do {
|
|
int bmw = -1;
|
|
int bmh = -1;
|
|
if(bmap != -1){
|
|
bm_get_info( bmap, &bmw, &bmh);
|
|
if((bmw != gr_screen.max_w_unscaled) || (bmh != gr_screen.max_h_unscaled)){
|
|
gr_clear();
|
|
}
|
|
} else {
|
|
gr_clear();
|
|
}
|
|
} while(false);
|
|
if(Multi_pxo_bitmap != -1){
|
|
gr_set_bitmap(Multi_pxo_bitmap);
|
|
gr_bitmap(0,0,GR_RESIZE_MENU);
|
|
}
|
|
Multi_pxo_window.draw();
|
|
|
|
// display the channel list
|
|
multi_pxo_blit_channels();
|
|
|
|
// display the player list
|
|
multi_pxo_blit_players();
|
|
|
|
// blit the chat text
|
|
multi_pxo_chat_blit();
|
|
|
|
// blit the status text
|
|
multi_pxo_blit_status_text();
|
|
|
|
// blit and process the notification string
|
|
multi_pxo_notify_blit();
|
|
|
|
// any bitmap or info or whatever
|
|
multi_pxo_ban_draw();
|
|
|
|
// draw any motd stuff
|
|
multi_pxo_motd_maybe_blit();
|
|
|
|
// if we have a valid animation handle, play it
|
|
// display the mission start countdown timer (if any)
|
|
//anim_render_all(GS_STATE_MULTI_MISSION_SYNC,flFrametime);
|
|
if(gameseq_get_state() == GS_STATE_PXO && Multi_pxo_anim.num_frames > 0)
|
|
generic_anim_render(&Multi_pxo_anim, flFrametime, MULTI_PXO_ANIM_X, MULTI_PXO_ANIM_Y, true);
|
|
}
|
|
|
|
// process common stuff
|
|
void multi_pxo_process_common(bool api_access)
|
|
{
|
|
// skip if we haven't logged into the tracker yet
|
|
if (Multi_pxo_must_validate) {
|
|
return;
|
|
}
|
|
|
|
// process the channel list (select, etc)
|
|
multi_pxo_process_channels(api_access);
|
|
|
|
// process the player list (select, etc)
|
|
multi_pxo_process_players(api_access);
|
|
|
|
// chat_process specifically handles checking the typed string in the
|
|
// ui input box and sending it. When running through the UI, that input
|
|
// box does not exist. So we do not need this one processor.
|
|
if (!api_access) {
|
|
// process chat controls
|
|
multi_pxo_chat_process();
|
|
}
|
|
|
|
// process http download details
|
|
multi_pxo_ban_process(api_access);
|
|
}
|
|
|
|
// 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,"");
|
|
}
|
|
}
|
|
|
|
static int open_url(const char *url)
|
|
{
|
|
#if defined(_WIN32) || defined(__APPLE__)
|
|
const char *open_cmd = "open";
|
|
#else
|
|
const char *open_cmd = "xdg-open";
|
|
#endif
|
|
|
|
char s_url[256];
|
|
|
|
// make sure it's a valid web uri
|
|
if ( !SDL_strncasecmp(url, "http://", 7) || !SDL_strncasecmp(url, "https://", 8) ) {
|
|
SDL_strlcpy(s_url, url, SDL_arraysize(s_url));
|
|
} else {
|
|
SDL_snprintf(s_url, SDL_arraysize(s_url), "http://%s", url);
|
|
}
|
|
|
|
#ifdef _WIN32
|
|
intptr_t rval = (intptr_t) ShellExecuteA(NULL, open_cmd, s_url, NULL, NULL, SW_SHOWNORMAL);
|
|
|
|
if (rval <= 32) {
|
|
return -1;
|
|
}
|
|
|
|
return 0;
|
|
#else
|
|
int statval = 0;
|
|
pid_t mpid = fork();
|
|
|
|
if (mpid < 0) {
|
|
// nothing, will return error
|
|
} else if (mpid == 0) {
|
|
int rv = 0;
|
|
|
|
rv = execlp(open_cmd, open_cmd, s_url, nullptr);
|
|
|
|
exit(rv);
|
|
} else {
|
|
waitpid(mpid, &statval, 0);
|
|
|
|
if ( WIFEXITED(statval) ) {
|
|
if (WEXITSTATUS(statval) == 0) {
|
|
return 0;
|
|
} else {
|
|
return -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
#endif
|
|
}
|
|
|
|
// fire up the given URL
|
|
void multi_pxo_url(const char *url)
|
|
{
|
|
if ( !url || !strlen(url) ) {
|
|
return;
|
|
}
|
|
|
|
static UI_TIMESTAMP click_timeout;
|
|
|
|
if ( click_timeout.isValid() && !ui_timestamp_elapsed(click_timeout) ) {
|
|
return;
|
|
}
|
|
|
|
if ( open_url(url) ) {
|
|
popup(PF_USE_AFFIRMATIVE_ICON | PF_TITLE_RED | PF_TITLE_BIG,1,POPUP_OK,XSTR("Warning\nCould not locate/launch default Internet Browser",943));
|
|
} else {
|
|
// short delay before allowing another click
|
|
click_timeout = ui_timestamp(750);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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;idx<MULTI_PXO_NUM_BUTTONS;idx++){
|
|
if(Multi_pxo_buttons[gr_screen.res][idx].button.pressed()){
|
|
multi_pxo_button_pressed(idx);
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a button press
|
|
*/
|
|
void multi_pxo_button_pressed(int n)
|
|
{
|
|
switch(n){
|
|
case MULTI_PXO_EXIT:
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
break;
|
|
|
|
case MULTI_PXO_CHAN_UP:
|
|
multi_pxo_scroll_channels_up();
|
|
break;
|
|
|
|
case MULTI_PXO_CHAN_DOWN:
|
|
multi_pxo_scroll_channels_down();
|
|
break;
|
|
|
|
case MULTI_PXO_TEXT_UP:
|
|
multi_pxo_scroll_chat_up();
|
|
break;
|
|
|
|
case MULTI_PXO_TEXT_DOWN:
|
|
multi_pxo_scroll_chat_down();
|
|
break;
|
|
|
|
case MULTI_PXO_PLIST_UP:
|
|
multi_pxo_scroll_players_up();
|
|
multi_pxo_chat_adjust_start();
|
|
break;
|
|
|
|
case MULTI_PXO_PLIST_DOWN:
|
|
multi_pxo_scroll_players_down();
|
|
multi_pxo_chat_adjust_start();
|
|
break;
|
|
|
|
case MULTI_PXO_JOIN:
|
|
multi_pxo_maybe_join_channel(&Multi_pxo_channels[Multi_pxo_channel_select]);
|
|
break;
|
|
|
|
case MULTI_PXO_GAMES:
|
|
// move to the join game screen as normally (temporary!)
|
|
gameseq_post_event( GS_EVENT_MULTI_JOIN_GAME );
|
|
break;
|
|
|
|
case MULTI_PXO_JOIN_PRIV:
|
|
// if we're not already trying to join, allow this
|
|
if(!SWITCHING_CHANNELS()){
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
|
|
// fire up the private join popup
|
|
multi_pxo_priv_popup();
|
|
} else {
|
|
multi_pxo_notify_add(XSTR("Already trying to join a channel!",945));
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
break;
|
|
|
|
case MULTI_PXO_FIND:
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
|
|
// fire up the find join popup
|
|
multi_pxo_find_popup();
|
|
break;
|
|
|
|
case MULTI_PXO_HELP:
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
gameseq_post_event(GS_EVENT_PXO_HELP);
|
|
break;
|
|
|
|
case MULTI_PXO_PINFO:
|
|
char stats[MAX_PXO_TEXT_LEN];
|
|
|
|
// if we have a guy selected, try and get his info
|
|
if((Multi_pxo_player_select >= 0) && (Multi_pxo_player_select < static_cast<int>(Multi_pxo_players.size()))){
|
|
// if we successfully got info for this guy
|
|
if (multi_pxo_maybe_get_player(Multi_pxo_players[Multi_pxo_player_select].c_str())) {
|
|
// 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_players[Multi_pxo_player_select].c_str());
|
|
popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,stats);
|
|
}
|
|
} else {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
break;
|
|
|
|
case MULTI_PXO_RANKINGS:
|
|
// make sure he doesn't click it too many times
|
|
if((Multi_pxo_ranking_last < 0.0f) || ((f2fl(timer_get_fixed_seconds()) - Multi_pxo_ranking_last) > MULTI_PXO_RANK_TIME) ){
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
|
|
// fire up the url
|
|
multi_pxo_url(Multi_options_g.pxo_rank_url);
|
|
|
|
// mark the time down
|
|
Multi_pxo_ranking_last = f2fl(timer_get_fixed_seconds());
|
|
} else {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
break;
|
|
|
|
case MULTI_PXO_MOTD:
|
|
// maybe fire up the pxo motd dialog
|
|
multi_pxo_motd_dialog();
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Condition function for popup_do_with_condition for connected to Parallax Online
|
|
*/
|
|
int mpxo_failed = 0;
|
|
int multi_pxo_connect_do()
|
|
{
|
|
int ret_code;
|
|
char id_string[MAX_PXO_TEXT_LEN] = "";
|
|
char ip_string[MAX_PXO_TEXT_LEN] = "";
|
|
|
|
// if we already tried and failed, sit around until the user presses cancel
|
|
if(!mpxo_failed){
|
|
// try and connect to the server
|
|
Assert(Player);
|
|
|
|
// build the tracker id string
|
|
memset(id_string, 0, MAX_PXO_TEXT_LEN);
|
|
sprintf_safe(id_string, "%s %s", Multi_tracker_id_string, Player->callsign);
|
|
|
|
// build the ip string
|
|
memset(ip_string, 0, MAX_PXO_TEXT_LEN);
|
|
if (snprintf(ip_string, MAX_PXO_TEXT_LEN, "%s:%d", Multi_options_g.pxo_ip, PXO_CHAT_PORT) >= MAX_PXO_TEXT_LEN) {
|
|
// Make sure the string is null terminated
|
|
ip_string[MAX_PXO_TEXT_LEN - 1] = '\0';
|
|
}
|
|
|
|
// connect to the server
|
|
ret_code = ConnectToChatServer(ip_string, Multi_pxo_nick, id_string);
|
|
|
|
// give some time to the pxo api.
|
|
multi_pxo_api_process();
|
|
|
|
switch(ret_code){
|
|
// already connected, return success
|
|
case -2:
|
|
return 10;
|
|
|
|
// failed to connect, return fail
|
|
case -1 :
|
|
mpxo_failed = 1;
|
|
return 1;
|
|
|
|
// connected, return success
|
|
case 1 :
|
|
return 10;
|
|
|
|
// still connecting
|
|
case 0 :
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Popup loop which does an autojoin of a public channel.
|
|
*
|
|
* Returns when the autojoin process is complete
|
|
*/
|
|
int multi_pxo_autojoin_do()
|
|
{
|
|
pxo_channel last_channel;
|
|
|
|
// if we need to autojoin, do so now
|
|
if (Multi_pxo_must_autojoin) {
|
|
Multi_pxo_must_autojoin = 0;
|
|
|
|
// if we're supposed to be using a (valid) "last" channel, do so
|
|
if ( Multi_pxo_use_last_channel && strlen(Multi_pxo_channel_last) ) {
|
|
// setup the data
|
|
memset(&last_channel, 0, sizeof(pxo_channel));
|
|
last_channel.num_users = 0;
|
|
strcpy_s(last_channel.name, Multi_pxo_channel_last);
|
|
|
|
// join the channel
|
|
multi_pxo_join_channel(&last_channel);
|
|
|
|
nprintf(("Network","PXO : using last channel\n"));
|
|
} else {
|
|
multi_pxo_autojoin();
|
|
|
|
nprintf(("Network","PXO : using autojoin channel\n"));
|
|
}
|
|
|
|
multi_pxo_get_channels();
|
|
}
|
|
|
|
// give some time to the pxo api.
|
|
multi_pxo_api_process();
|
|
multi_pxo_process_common();
|
|
|
|
// next value is not -1 when actually switching channels, so keep processing by returning 0.
|
|
if ( SWITCHING_CHANNELS() )
|
|
return 0;
|
|
|
|
// couldn't switch channel for some reason. bail out with -1
|
|
if ( !ON_CHANNEL() )
|
|
return -1;
|
|
|
|
// return success
|
|
return 1;
|
|
}
|
|
|
|
/**
|
|
* Attempt to connect to Parallax Online, return success or fail
|
|
*/
|
|
int multi_pxo_connect()
|
|
{
|
|
char join_str[256];
|
|
char join_fail_str[256];
|
|
|
|
// intiialize chat api
|
|
ChatInit();
|
|
|
|
// set us to "must autojoin"
|
|
Multi_pxo_must_autojoin = 1;
|
|
|
|
// run the connect dialog/popup
|
|
mpxo_failed = 0;
|
|
|
|
if ( popup_till_condition(multi_pxo_connect_do, XSTR("&Cancel", 779), XSTR("Logging into Parallax Online", 949)) == 10 ) {
|
|
int rval;
|
|
|
|
memset(join_str, 0, 256);
|
|
memset(join_fail_str, 0, 256);
|
|
|
|
// if we're going to use the "last" channel
|
|
if ( Multi_pxo_use_last_channel && strlen(Multi_pxo_channel_last) ) {
|
|
strcpy_s(join_str, XSTR("Joining last channel (", 982));
|
|
strcat_s(join_str, Multi_pxo_channel_last + 1);
|
|
strcat_s(join_str, ")");
|
|
|
|
strcpy_s(join_fail_str, XSTR("Unable to join last channel", 983));
|
|
} else {
|
|
strcpy_s(join_str, XSTR("Autojoining public channel", 984));
|
|
strcpy_s(join_fail_str, XSTR("Unable to autojoin public channel", 985));
|
|
}
|
|
|
|
// once connected, we should do an autojoin before allowing the guy to continue.
|
|
rval = popup_till_condition( multi_pxo_autojoin_do, XSTR("&Cancel", 779), join_str );
|
|
|
|
if ( rval == 1 )
|
|
return 1;
|
|
|
|
popup(PF_USE_AFFIRMATIVE_ICON, 1, XSTR("OK", 1492), join_fail_str);
|
|
}
|
|
|
|
// otherwise disconnect just to be safe
|
|
DisconnectFromChatServer();
|
|
|
|
// we failed to connect, so give a nice popup about that
|
|
if (mpxo_failed) {
|
|
popup(PF_USE_AFFIRMATIVE_ICON, 1, POPUP_OK, XSTR("Failed to connect to Parallax Online!", 947));
|
|
}
|
|
|
|
// if we are coming from the mainhall then fail to the join game screen rather
|
|
// than keeping the user constantly at the mainhall
|
|
if (gameseq_get_previous_state() == GS_STATE_MAIN_MENU) {
|
|
gameseq_post_event(GS_EVENT_MULTI_JOIN_GAME);
|
|
} else {
|
|
gameseq_post_event(GS_EVENT_MAIN_MENU);
|
|
}
|
|
|
|
// did not successfully connect
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Run the networking functions for the PXO API
|
|
*/
|
|
void multi_pxo_api_process()
|
|
{
|
|
char *p;
|
|
char msg_str[512];
|
|
Chat_command *cmd;
|
|
pxo_channel *lookup;
|
|
|
|
// give some time to psnet
|
|
PSNET_TOP_LAYER_PROCESS();
|
|
|
|
// give some time to the game tracker API
|
|
IdleGameTracker();
|
|
|
|
// give some time to the user tracker API
|
|
PollPTrackNet();
|
|
|
|
// get any incoming text
|
|
do
|
|
{
|
|
p = GetChatText();
|
|
|
|
if (p) {
|
|
// process the chat line
|
|
multi_pxo_chat_process_incoming(p);
|
|
}
|
|
} while(p);
|
|
|
|
// get any incoming channel list stuff
|
|
p = GetChannelList();
|
|
|
|
if (p) {
|
|
multi_pxo_make_channels(p);
|
|
}
|
|
|
|
// process any chat commands
|
|
cmd = GetChatCommand();
|
|
|
|
while (cmd) {
|
|
switch (cmd->command)
|
|
{
|
|
case CC_USER_JOINING:
|
|
// add a user, if he doesn't already exist
|
|
if (multi_pxo_find_player(cmd->data) < 0)
|
|
multi_pxo_add_player(cmd->data);
|
|
|
|
// increase the player count
|
|
if (ON_CHANNEL() ) {
|
|
lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name);
|
|
|
|
if (lookup != nullptr)
|
|
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);
|
|
|
|
if (lookup != nullptr)
|
|
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(nullptr);
|
|
|
|
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);
|
|
|
|
if (lookup == nullptr) {
|
|
// 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);
|
|
}
|
|
|
|
// set the user count to be 0
|
|
if (lookup != nullptr)
|
|
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;
|
|
|
|
// get the new string
|
|
from = strtok(data," ");
|
|
to = strtok(nullptr,"");
|
|
if((from != nullptr) && (to != nullptr)){
|
|
int ply = multi_pxo_find_player(from);
|
|
if(ply > 0){
|
|
Multi_pxo_players[ply] = 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);
|
|
|
|
if (lookup != nullptr) {
|
|
lookup->num_servers = (ushort)count;
|
|
|
|
nprintf(("Network","PXO : updated channel %s server count to %d\n",name,count));
|
|
ml_printf("PXO : updated channel %s server count to %d", name, count);
|
|
} else {
|
|
ml_printf("PXO : unable to locate channel when trying to update count for %s", name);
|
|
}
|
|
}
|
|
|
|
// status bar stuff -----------------------------------------------
|
|
|
|
/**
|
|
* Set the status text
|
|
*/
|
|
void multi_pxo_set_status_text(const char *txt)
|
|
{
|
|
// copy in the text
|
|
memset(Multi_pxo_status_text, 0, MAX_PXO_TEXT_LEN);
|
|
strncpy(Multi_pxo_status_text, txt, MAX_PXO_TEXT_LEN-1);
|
|
|
|
// make sure it fits properly
|
|
font::force_fit_string(Multi_pxo_status_text, MAX_PXO_TEXT_LEN-1, Multi_pxo_status_coords[gr_screen.res][2]);
|
|
}
|
|
|
|
/**
|
|
* Blit the status text
|
|
*/
|
|
void multi_pxo_blit_status_text()
|
|
{
|
|
int w;
|
|
|
|
// center and draw the text
|
|
if(strlen(Multi_pxo_status_text)) {
|
|
gr_set_color_fast(&Color_bright);
|
|
gr_get_string_size(&w, NULL, Multi_pxo_status_text);
|
|
gr_string(Multi_pxo_status_coords[gr_screen.res][0] + ((Multi_pxo_status_coords[gr_screen.res][2] - w)/2), Multi_pxo_status_coords[gr_screen.res][1], Multi_pxo_status_text, GR_RESIZE_MENU);
|
|
}
|
|
}
|
|
|
|
|
|
// channel related stuff -------------------------------------------
|
|
|
|
/**
|
|
* Get a list of channels on the server
|
|
*/
|
|
void multi_pxo_get_channels()
|
|
{
|
|
SendChatString(NOX("/list"));
|
|
}
|
|
|
|
/**
|
|
* Clear the old channel list
|
|
*/
|
|
void multi_pxo_clear_channels()
|
|
{
|
|
// only clear a non-null list
|
|
if(!Multi_pxo_channels.empty()){
|
|
Multi_pxo_channels.clear();
|
|
Multi_pxo_channels.shrink_to_fit();
|
|
|
|
// item we're going to start displaying at
|
|
Multi_pxo_channel_start = 0;
|
|
Multi_pxo_channel_start_index = -1;
|
|
|
|
// items we've currently got selected
|
|
Multi_pxo_channel_select = 0;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 == nullptr){
|
|
return;
|
|
}
|
|
name_tok += 1;
|
|
do {
|
|
// parse the user count token
|
|
user_tok = strtok(nullptr," ");
|
|
|
|
// parse the channel description token
|
|
desc_tok = strtok(nullptr,"$");
|
|
|
|
// something invalid in the data, return here.....
|
|
if((name_tok == nullptr) || (user_tok == nullptr) || (desc_tok == nullptr)){
|
|
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);
|
|
|
|
if(lookup != nullptr){
|
|
lookup->num_users = (short)num_users;
|
|
}
|
|
// add the channel
|
|
else {
|
|
res = multi_pxo_add_channel(name_tok);
|
|
if(res != nullptr){
|
|
res->num_users = (short)num_users;
|
|
strcpy_s(res->desc,desc_tok);
|
|
}
|
|
}
|
|
}
|
|
|
|
// get the next name token
|
|
name_tok = strtok(nullptr," ");
|
|
} while(name_tok != nullptr);
|
|
|
|
// 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);
|
|
if(lookup == nullptr){
|
|
// 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);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 channel;
|
|
strcpy_s(channel.name, name);
|
|
strcpy_s(channel.desc, "");
|
|
channel.num_users = -1;
|
|
channel.num_servers = 0;
|
|
Multi_pxo_channels.push_back(channel);
|
|
return &Multi_pxo_channels.back();
|
|
}
|
|
|
|
/**
|
|
* Lookup a channel with the specified name
|
|
*/
|
|
pxo_channel *multi_pxo_find_channel(char *name)
|
|
{
|
|
if(Multi_pxo_channels.size() == 0){
|
|
return nullptr;
|
|
}
|
|
|
|
for (size_t i = 0; i < Multi_pxo_channels.size(); i++) {
|
|
pxo_channel channel = Multi_pxo_channels[i];
|
|
if (!stricmp(name, channel.name)) {
|
|
return &Multi_pxo_channels[i];
|
|
}
|
|
}
|
|
|
|
return nullptr;
|
|
}
|
|
|
|
/**
|
|
* Process the channel list (select, etc)
|
|
*/
|
|
void multi_pxo_process_channels(bool api_access)
|
|
{
|
|
int item_index,my;
|
|
int idx;
|
|
|
|
// the first part of this function works under the assumption that the list has been initialized.
|
|
if (Multi_pxo_channels.size() > 0) {
|
|
|
|
// if we don't have a start item
|
|
if (Multi_pxo_channel_start < 0) {
|
|
Multi_pxo_channel_start = 0;
|
|
Multi_pxo_channel_start_index = 0;
|
|
}
|
|
|
|
// if we don't have a selected item
|
|
if(Multi_pxo_channel_select < 0){
|
|
Multi_pxo_channel_select = 0;
|
|
|
|
// set the text
|
|
multi_pxo_set_status_text(Multi_pxo_channels[Multi_pxo_channel_select].desc);
|
|
}
|
|
|
|
// if the "switch" delay timestamp is set, see if it has expired
|
|
if(Multi_pxo_switch_delay.isValid() && ui_timestamp_elapsed(Multi_pxo_switch_delay)){
|
|
Multi_pxo_switch_delay = UI_TIMESTAMP::invalid();
|
|
}
|
|
|
|
// see if we have a mouse click on the channel region
|
|
if(!api_access && Multi_pxo_channel_button.pressed()){
|
|
Multi_pxo_channel_button.get_mouse_pos(nullptr,&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) < static_cast<int>(Multi_pxo_channels.size())){
|
|
Multi_pxo_channel_select = Multi_pxo_channel_start;
|
|
for(idx=0;idx<item_index;idx++){
|
|
Multi_pxo_channel_select++;
|
|
}
|
|
|
|
// set the text
|
|
multi_pxo_set_status_text(Multi_pxo_channels[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()
|
|
{
|
|
filter_game_list_struct filter;
|
|
|
|
if (Multi_pxo_channels.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
// traverse the list of existing channels we know about and query the game tracker about them
|
|
for (auto &channel : Multi_pxo_channels) {
|
|
if (strlen(channel.name)) {
|
|
// copy in the info
|
|
memset(&filter, 0, sizeof(filter_game_list_struct));
|
|
SDL_strlcpy(filter.channel, channel.name, SDL_arraysize(filter.channel));
|
|
|
|
// send the request
|
|
RequestGameCountWithFilter(&filter);
|
|
}
|
|
}
|
|
|
|
// record the time
|
|
Multi_pxo_channel_server_refresh = f2fl(timer_get_fixed_seconds());
|
|
}
|
|
|
|
/**
|
|
* Refresh current channel server count
|
|
*/
|
|
void multi_pxo_channel_refresh_current()
|
|
{
|
|
// send a request for a server count on this channel
|
|
if ( strlen(Multi_pxo_channel_current.name) ) {
|
|
// fill in the data
|
|
filter_game_list_struct filter;
|
|
memset(&filter, 0, sizeof(filter_game_list_struct));
|
|
SDL_strlcpy(filter.channel, Multi_pxo_channel_current.name, SDL_arraysize(filter.channel));
|
|
|
|
// send the request
|
|
RequestGameCountWithFilter(&filter);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display the channel list
|
|
*/
|
|
void multi_pxo_blit_channels()
|
|
{
|
|
char chan_name[MAX_PXO_TEXT_LEN];
|
|
char chan_users[15];
|
|
char chan_servers[15];
|
|
int user_w,server_w;
|
|
int y_start;
|
|
int line_height = gr_get_font_height() + 1;
|
|
|
|
// blit as many channels as we can
|
|
y_start = Multi_pxo_chan_coords[gr_screen.res][1];
|
|
if (Multi_pxo_channel_start < 0) {
|
|
return;
|
|
}
|
|
for (int i = 0; i < static_cast<int>(Multi_pxo_channels.size()); i++) {
|
|
// if this is the currently selected item, highlight it
|
|
if(i == Multi_pxo_channel_select){
|
|
gr_set_color_fast(&Color_bright);
|
|
}
|
|
// otherwise draw it normally
|
|
else {
|
|
gr_set_color_fast(&Color_normal);
|
|
}
|
|
|
|
pxo_channel *channel = &Multi_pxo_channels[i];
|
|
|
|
// get the # of users on the channel
|
|
memset(chan_users, 0, 15);
|
|
sprintf(chan_users, "%d", channel->num_users);
|
|
|
|
// get the width of the user count string
|
|
gr_get_string_size(&user_w, nullptr, chan_users);
|
|
|
|
// get the # of servers on the channel
|
|
memset(chan_servers,0,15);
|
|
sprintf(chan_servers, "%d", channel->num_servers);
|
|
|
|
// get the width of the user count string
|
|
gr_get_string_size(&server_w, nullptr, chan_servers);
|
|
|
|
// make sure the name fits
|
|
memset(chan_name, 0, MAX_PXO_TEXT_LEN);
|
|
Assert(channel->name);
|
|
strcpy_s(chan_name,channel->name);
|
|
font::force_fit_string(chan_name, MAX_PXO_TEXT_LEN-1, Multi_pxo_chan_coords[gr_screen.res][2] - Multi_pxo_chan_column_offsets[gr_screen.res][CHAN_PLAYERS_COLUMN]);
|
|
|
|
// blit the strings
|
|
gr_string(Multi_pxo_chan_coords[gr_screen.res][0], y_start, chan_name + 1, GR_RESIZE_MENU);
|
|
gr_string(Multi_pxo_chan_coords[gr_screen.res][0] + Multi_pxo_chan_coords[gr_screen.res][2] - Multi_pxo_chan_column_offsets[gr_screen.res][CHAN_PLAYERS_COLUMN], y_start, chan_users, GR_RESIZE_MENU);
|
|
gr_set_color_fast(&Color_bright);
|
|
gr_string(Multi_pxo_chan_coords[gr_screen.res][0] + Multi_pxo_chan_coords[gr_screen.res][2] - Multi_pxo_chan_column_offsets[gr_screen.res][CHAN_GAMES_COLUMN], y_start, chan_servers, GR_RESIZE_MENU);
|
|
|
|
// increment the displayed count
|
|
y_start += line_height;
|
|
|
|
};
|
|
}
|
|
|
|
/**
|
|
* 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 == 0){
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
return;
|
|
}
|
|
|
|
// otherwise move up one
|
|
Multi_pxo_channel_start--;
|
|
Multi_pxo_channel_start_index--;
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
}
|
|
|
|
/**
|
|
* Scroll channel list down
|
|
*/
|
|
void multi_pxo_scroll_channels_down()
|
|
{
|
|
// if we're already at the tail of the list, do nothing
|
|
if(Multi_pxo_channel_start == static_cast<int>(Multi_pxo_channels.size() -1)){
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
return;
|
|
}
|
|
|
|
// if we can't scroll further without going past the end of the viewable list, don't
|
|
if((Multi_pxo_channel_start_index + gr_get_dynamic_font_lines(Multi_pxo_max_chan_display[gr_screen.res]) >= static_cast<int>(Multi_pxo_channels.size()))){
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
return;
|
|
}
|
|
|
|
// otherwise move down one
|
|
Multi_pxo_channel_start++;
|
|
Multi_pxo_channel_start_index++;
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
}
|
|
|
|
/**
|
|
* Attempt to join a channel
|
|
*/
|
|
void multi_pxo_join_channel(pxo_channel *chan)
|
|
{
|
|
char switch_msg[256];
|
|
|
|
// if we're already on this channel, do nothing
|
|
if(ON_CHANNEL() && !stricmp(chan->name, Multi_pxo_channel_current.name)){
|
|
return;
|
|
}
|
|
|
|
// if we're already trying to join a channel, do nothing
|
|
if(SWITCHING_CHANNELS()){
|
|
return;
|
|
}
|
|
|
|
// try and join the channel
|
|
switch(SetNewChatChannel(chan->name)){
|
|
case -1 :
|
|
Int3();
|
|
break;
|
|
|
|
case 0 :
|
|
// decrement the count of our current channel
|
|
pxo_channel *lookup;
|
|
lookup = multi_pxo_find_channel(Multi_pxo_channel_current.name);
|
|
if(lookup != nullptr){
|
|
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();
|
|
}
|
|
}
|
|
|
|
void multi_pxo_maybe_join_channel(pxo_channel* chan)
|
|
{
|
|
// if there are no channels to join, let the user know
|
|
if (Multi_pxo_channels.size() == 0) {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
multi_pxo_notify_add(XSTR("No channels!", 944));
|
|
return;
|
|
}
|
|
|
|
// if we're not already trying to join, allow this
|
|
if (!SWITCHING_CHANNELS() && (Multi_pxo_channel_select >= 0)) {
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
multi_pxo_join_channel(chan);
|
|
} else {
|
|
multi_pxo_notify_add(XSTR("Already trying to join a channel!", 945));
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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(nullptr)){
|
|
// 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);
|
|
if(lookup == nullptr){
|
|
// 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);
|
|
}
|
|
|
|
// set the user count to be 1 (just me)
|
|
if(lookup != nullptr){
|
|
lookup->num_users = 1;
|
|
}
|
|
|
|
// set the "switch" delay timestamp
|
|
Multi_pxo_switch_delay = ui_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()
|
|
{
|
|
Multi_pxo_players.clear();
|
|
Multi_pxo_players.shrink_to_fit();
|
|
Multi_pxo_player_start = 0;
|
|
Multi_pxo_player_select = -1;
|
|
}
|
|
|
|
/**
|
|
* Create a new player with the given name and place it on the player list, return a pointer or NULL on fail
|
|
*/
|
|
void multi_pxo_add_player(const char *name)
|
|
{
|
|
SCP_string new_player = name;
|
|
Multi_pxo_players.push_back(new_player);
|
|
}
|
|
|
|
/**
|
|
* Remove a player with the given name
|
|
*/
|
|
void multi_pxo_del_player(const char *name)
|
|
{
|
|
if (Multi_pxo_players.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
// find and erase the player from the list
|
|
for (size_t i = 0; i < Multi_pxo_players.size(); i++) {
|
|
if (!stricmp(Multi_pxo_players[i].c_str(), name)) {
|
|
Multi_pxo_player_select = -1; // unselect
|
|
Multi_pxo_player_start--;
|
|
Multi_pxo_players.erase(Multi_pxo_players.begin() + i);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
/**
|
|
* Try and find a player with the given name, return a pointer to his entry (or NULL)
|
|
*/
|
|
int multi_pxo_find_player(const char *name)
|
|
{
|
|
if (Multi_pxo_players.empty()) {
|
|
return -1;
|
|
}
|
|
|
|
for (int i = 0; i < static_cast<int>(Multi_pxo_players.size()); i++) {
|
|
if (!stricmp(Multi_pxo_players[i].c_str(), name)) {
|
|
return i;
|
|
}
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
/**
|
|
* Process the player list (select, etc)
|
|
*/
|
|
void multi_pxo_process_players(bool api_access)
|
|
{
|
|
int item_index,my;
|
|
|
|
// if we don't have a start item, but the list is non-null
|
|
if(Multi_pxo_player_start_index < 0){
|
|
Multi_pxo_player_start = 0;
|
|
Multi_pxo_player_start_index = 0;
|
|
}
|
|
|
|
// see if we have a mouse click on the channel region
|
|
if(!api_access && Multi_pxo_player_button.pressed()){
|
|
Multi_pxo_player_button.get_mouse_pos(nullptr,&my);
|
|
|
|
// index from the top
|
|
item_index = my / (gr_get_font_height() + 1);
|
|
|
|
// select the item if possible
|
|
if ((item_index + Multi_pxo_player_start_index) < static_cast<int>(Multi_pxo_players.size())) {
|
|
Multi_pxo_player_select = Multi_pxo_player_start_index;
|
|
for (int idx = 0; idx < item_index; idx++) {
|
|
Multi_pxo_player_select++;
|
|
};
|
|
}
|
|
|
|
if ((Multi_pxo_player_select < 0) || (Multi_pxo_player_select >= static_cast<int>(Multi_pxo_players.size()))) {
|
|
Multi_pxo_player_select = -1;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Display the player list
|
|
*/
|
|
void multi_pxo_blit_players()
|
|
{
|
|
int disp_count,y_start;
|
|
int line_height = gr_get_font_height() + 1;
|
|
|
|
// blit as many players as we can
|
|
disp_count = 0;
|
|
y_start = Multi_pxo_player_coords[gr_screen.res][1];
|
|
if(Multi_pxo_players.size() == 0){
|
|
return;
|
|
}
|
|
|
|
for (int i = 0; i < static_cast<int>(Multi_pxo_players.size()); i++) {
|
|
if (disp_count < gr_get_dynamic_font_lines(Multi_pxo_max_player_display[gr_screen.res])) {
|
|
if (i == Multi_pxo_player_select) {
|
|
gr_set_color_fast(&Color_bright);
|
|
} else {
|
|
gr_set_color_fast(&Color_normal);
|
|
}
|
|
|
|
// make sure the string fits
|
|
char player_name[MAX_PXO_TEXT_LEN];
|
|
strcpy_s(player_name, Multi_pxo_players[i].c_str());
|
|
font::force_fit_string(player_name, MAX_PXO_TEXT_LEN - 1, Multi_pxo_player_coords[gr_screen.res][2]);
|
|
|
|
// blit the string
|
|
gr_string(Multi_pxo_player_coords[gr_screen.res][0], y_start, player_name, GR_RESIZE_MENU);
|
|
|
|
// increment the displayed count
|
|
disp_count++;
|
|
y_start += line_height;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 == 0){
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
return;
|
|
}
|
|
|
|
// otherwise move up one
|
|
Multi_pxo_player_start--;
|
|
Multi_pxo_player_start_index--;
|
|
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
}
|
|
|
|
/**
|
|
* Scroll player list down
|
|
*/
|
|
void multi_pxo_scroll_players_down()
|
|
{
|
|
|
|
// if we're already at the tail of the list, do nothing
|
|
if (Multi_pxo_player_start == static_cast<int>(Multi_pxo_players.size() - 1)) {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
return;
|
|
}
|
|
|
|
// if we can't scroll further without going past the end of the viewable list, don't
|
|
if ((Multi_pxo_player_start_index + gr_get_dynamic_font_lines(Multi_pxo_max_player_display[gr_screen.res]) >=
|
|
static_cast<int>(Multi_pxo_players.size()))) {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
return;
|
|
}
|
|
|
|
// otherwise move down one
|
|
Multi_pxo_player_start++;
|
|
Multi_pxo_player_start_index++;
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
}
|
|
|
|
|
|
// chat text stuff -----------------------------------------
|
|
|
|
/**
|
|
* Clear all lines of chat text in the chat area
|
|
*/
|
|
void multi_pxo_chat_clear()
|
|
{
|
|
// clear the text in all the lines
|
|
Multi_pxo_chat.clear();
|
|
Multi_pxo_chat_start = 0;
|
|
Multi_pxo_chat_slider.set_numberItems(0);
|
|
}
|
|
|
|
/**
|
|
* Add a line of text
|
|
*/
|
|
void multi_pxo_chat_add_line(const char *txt, int mode)
|
|
{
|
|
chat_line temp;
|
|
strcpy_s(temp.text, txt);
|
|
temp.mode = mode;
|
|
|
|
if (Multi_pxo_chat.size() >= MAX_CHAT_LINES) {
|
|
Multi_pxo_chat.erase(Multi_pxo_chat.begin());
|
|
}
|
|
|
|
Multi_pxo_chat.push_back(temp);
|
|
|
|
// if we've reached max chat lines, don't increment
|
|
int count = static_cast<int>(Multi_pxo_chat.size());
|
|
|
|
// set the count
|
|
Multi_pxo_chat_slider.set_numberItems(count > gr_get_dynamic_font_lines(Multi_pxo_max_chat_display[gr_screen.res]) ? count : 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[MULTI_PXO_CHAT_LINE_LEN],line[MULTI_PXO_CHAT_LINE_LEN];
|
|
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.isValid() && !ui_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, MULTI_PXO_CHAT_LINE_LEN);
|
|
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<n_lines; idx++){
|
|
memcpy(line,p_str[idx],n_chars[idx]);
|
|
line[n_chars[idx]] = '\0';
|
|
|
|
// unless the current mode is server or "switching channels", make all these CHAT_MODE_CARRY
|
|
if((mode != CHAT_MODE_SERVER) && (mode != CHAT_MODE_CHANNEL_SWITCH)){
|
|
mode = CHAT_MODE_CARRY;
|
|
}
|
|
multi_pxo_chat_add_line(line, mode);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Blit the chat text
|
|
*/
|
|
void multi_pxo_chat_blit()
|
|
{
|
|
int token_width;
|
|
|
|
// blit the title line
|
|
char title[MAX_PXO_TEXT_LEN];
|
|
memset(title,0,MAX_PXO_TEXT_LEN);
|
|
if(ON_CHANNEL()){
|
|
if(strlen(Multi_pxo_channel_current.name) > 1){
|
|
sprintf(title, XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name+1); // [[ <who> on <channel> ]]
|
|
} else {
|
|
sprintf(title, XSTR("%s on %s", 955), Multi_pxo_nick, Multi_pxo_channel_current.name); // [[ <who> on <channel> ]]
|
|
}
|
|
} else {
|
|
strcpy_s(title,XSTR("Parallax Online - No Channel", 956));
|
|
}
|
|
font::force_fit_string(title, MAX_PXO_TEXT_LEN-1, Multi_pxo_chat_coords[gr_screen.res][2] - 10);
|
|
gr_get_string_size(&token_width,nullptr,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);
|
|
|
|
int disp_count, y_start;
|
|
int line_height = gr_get_font_height() + 1;
|
|
|
|
// blit as many chat lines as we can
|
|
disp_count = 0;
|
|
y_start = Multi_pxo_chat_coords[gr_screen.res][1];
|
|
if (Multi_pxo_chat.size() == 0) {
|
|
return;
|
|
}
|
|
|
|
for (int i = Multi_pxo_chat_start; i < static_cast<int>(Multi_pxo_chat.size()); i++) {
|
|
if (disp_count < gr_get_dynamic_font_lines(Multi_pxo_max_chat_display[gr_screen.res])) {
|
|
SCP_list<chat_line>::iterator line = Multi_pxo_chat.begin();
|
|
std::advance(line, i);
|
|
char* tok;
|
|
switch (line->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, line->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, line->text, GR_RESIZE_MENU);
|
|
break;
|
|
|
|
// normal mode, just highlight the server
|
|
case CHAT_MODE_PRIVATE:
|
|
case CHAT_MODE_NORMAL:
|
|
char piece[MAX_CHAT_LINE_LEN + 1];
|
|
strcpy_s(piece, line->text);
|
|
tok = strtok(piece, " ");
|
|
if (tok != nullptr) {
|
|
// get the width of just the first "piece"
|
|
gr_get_string_size(&token_width, nullptr, 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(nullptr, "");
|
|
if (tok != nullptr) {
|
|
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, line->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, line->text, GR_RESIZE_MENU);
|
|
break;
|
|
}
|
|
|
|
// increment the displayed count
|
|
disp_count++;
|
|
y_start += line_height;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Scroll to the very bottom of the chat area
|
|
*/
|
|
void multi_pxo_goto_bottom()
|
|
{
|
|
// if we have less than the displayable amount of lines, do nothing
|
|
if(static_cast<int>(Multi_pxo_chat.size()) <= gr_get_dynamic_font_lines(Multi_pxo_max_chat_display[gr_screen.res])){
|
|
// nothing to do for the slider
|
|
Multi_pxo_chat_slider.set_numberItems(0);
|
|
return;
|
|
}
|
|
|
|
if (multi_pxo_can_scroll_down())
|
|
{
|
|
// otherwise move back the right # of items
|
|
Multi_pxo_chat_start = static_cast<int>(Multi_pxo_chat.size()) - gr_get_dynamic_font_lines(Multi_pxo_max_chat_display[gr_screen.res]);
|
|
|
|
// 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 == 0) {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
return;
|
|
}
|
|
|
|
// otherwise move up one
|
|
Multi_pxo_chat_start--;
|
|
multi_pxo_chat_adjust_start();
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
}
|
|
|
|
/**
|
|
* Returns 1 if we can scroll down, 0 otherwise
|
|
*/
|
|
bool multi_pxo_can_scroll_down()
|
|
{
|
|
// check if we can move down, return accordingly
|
|
if (static_cast<int>(Multi_pxo_chat.size()) > gr_get_dynamic_font_lines(Multi_pxo_max_chat_display[gr_screen.res])) {
|
|
if (Multi_pxo_chat_start < (static_cast<int>(Multi_pxo_chat.size()) - gr_get_dynamic_font_lines(Multi_pxo_max_chat_display[gr_screen.res]))){
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* 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_adjust_start();
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
} else {
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Send a chat message
|
|
*/
|
|
void multi_pxo_chat_send(const char* msg)
|
|
{
|
|
// if we're connected to a channel, send the chat to the server
|
|
if (ON_CHANNEL()) {
|
|
const char* result = SendChatString(msg, 1);
|
|
if (result != NULL) {
|
|
multi_pxo_chat_process_incoming(result);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process chat controls
|
|
*/
|
|
void multi_pxo_chat_process()
|
|
{
|
|
const char *remainder;
|
|
char msg[512];
|
|
int msg_pixel_width;
|
|
|
|
// Bail if the input box is not ready to go
|
|
if (!Multi_pxo_chat_input.is_valid()) {
|
|
return;
|
|
}
|
|
|
|
// if the chat line is getting too long, fire off the message, putting the last
|
|
// word on the next input line.
|
|
memset(msg, 0, 512);
|
|
Multi_pxo_chat_input.get_text(msg);
|
|
remainder = "";
|
|
|
|
// determine if the width of the string in pixels is > than the inputbox width -- if so,
|
|
// then send the message
|
|
gr_get_string_size(&msg_pixel_width, NULL, msg);
|
|
if ( msg_pixel_width >= (Multi_pxo_input_coords[gr_screen.res][2])) {
|
|
auto last_space = strrchr(msg, ' ');
|
|
if ( last_space ) {
|
|
*last_space = '\0';
|
|
remainder = last_space + 1;
|
|
} else {
|
|
remainder = "";
|
|
}
|
|
|
|
// if we're connected to a channel, send the chat to the server
|
|
multi_pxo_chat_send(msg);
|
|
|
|
// display any remainder of text on the next line
|
|
if (ON_CHANNEL()) {
|
|
Multi_pxo_chat_input.set_text(remainder);
|
|
} else {
|
|
Multi_pxo_chat_input.set_text("");
|
|
}
|
|
} else if((Multi_pxo_chat_input.pressed() && (msg[0] != '\0')) || (strlen(msg) >= MAX_CHAT_LINE_LEN)) {
|
|
// tack on the null terminator in the boundary case
|
|
size_t x = strlen(msg);
|
|
if(x >= MAX_CHAT_LINE_LEN){
|
|
msg[MAX_CHAT_LINE_LEN-1] = '\0';
|
|
}
|
|
|
|
// ignore "/nick" commands
|
|
if(multi_pxo_is_nick_command(msg)){
|
|
Multi_pxo_chat_input.set_text("");
|
|
return;
|
|
}
|
|
|
|
// send the chat to the server
|
|
// if we're connected to a channel, send the chat to the server
|
|
multi_pxo_chat_send(msg);
|
|
|
|
// display any remainder of text on the next line
|
|
if (ON_CHANNEL()) {
|
|
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()
|
|
{
|
|
// if we have no chat
|
|
if (static_cast<int>(Multi_pxo_chat.size()) <= gr_get_dynamic_font_lines(Multi_pxo_max_chat_display[gr_screen.res])) {
|
|
Multi_pxo_chat_start = 0;
|
|
return;
|
|
} else {
|
|
Multi_pxo_chat_slider.force_currentItem(Multi_pxo_chat_start);
|
|
}
|
|
}
|
|
|
|
// motd stuff ---------------------------------------------------------
|
|
|
|
/**
|
|
* Initialize motd when going into this screen
|
|
*/
|
|
void multi_pxo_motd_init()
|
|
{
|
|
// zero the motd string
|
|
strcpy_s(Pxo_motd, "");
|
|
|
|
// haven't gotten it yet
|
|
Pxo_motd_end = 0;
|
|
|
|
// haven't read it yet either
|
|
Pxo_motd_read = 0;
|
|
}
|
|
|
|
/**
|
|
* Set the motd text
|
|
*/
|
|
void multi_pxo_motd_add_text(const char *text)
|
|
{
|
|
size_t cur_len = strlen(Pxo_motd);
|
|
size_t new_len;
|
|
|
|
// sanity
|
|
if(text == NULL){
|
|
return;
|
|
}
|
|
|
|
// make sure its motd text
|
|
Assert(multi_pxo_is_motd_text(text));
|
|
if(!multi_pxo_is_motd_text(text)){
|
|
return;
|
|
}
|
|
|
|
// if its a 0 line motd
|
|
if(strlen(text) <= motd_prefix_len){
|
|
return;
|
|
}
|
|
|
|
// add text to the motd
|
|
new_len = strlen(text + motd_prefix_len) - 1;
|
|
if((cur_len + new_len + 1) < MAX_PXO_MOTD_LEN){
|
|
strcat_s(Pxo_motd, text + motd_prefix_len + 1);
|
|
strcat_s(Pxo_motd, "\n");
|
|
mprintf(("MOTD ADD : %s\n", Pxo_motd));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set end of motd
|
|
*/
|
|
void multi_pxo_set_end_of_motd()
|
|
{
|
|
int blink = 1;
|
|
|
|
Pxo_motd_end = 1;
|
|
mprintf(("MOTD ALL : %s\n", Pxo_motd));
|
|
|
|
Pxo_motd_read = 0;
|
|
|
|
// do we have an old MOTD file laying around? If so, read it in and see if its the same
|
|
uint old_chksum;
|
|
uint new_chksum;
|
|
|
|
// checksum the current motd
|
|
new_chksum = cf_add_chksum_long(0, (ubyte*)Pxo_motd, strlen(Pxo_motd));
|
|
|
|
// checksum the old motd if its lying around
|
|
CFILE *in = cfopen("oldmotd.txt", "rb");
|
|
if(in != NULL){
|
|
// read the old checksum
|
|
cfread(&old_chksum, sizeof(old_chksum), 1, in);
|
|
cfclose(in);
|
|
|
|
// same checksum? no blink
|
|
if(new_chksum == old_chksum){
|
|
blink = 0;
|
|
}
|
|
}
|
|
|
|
// write out the motd for next time
|
|
if(strlen(Pxo_motd)){
|
|
CFILE *out = cfopen("oldmotd.txt", "wb", 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 = UI_TIMESTAMP::invalid();
|
|
if(blink){
|
|
Pxo_motd_blink_on = 0;
|
|
if(!Pxo_motd_blinked_already){
|
|
Pxo_motd_blink_stamp = ui_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.isValid()){
|
|
// if the timestamp elapsed, flip the blink flag
|
|
if(ui_timestamp_elapsed(Pxo_motd_blink_stamp)){
|
|
Pxo_motd_blink_on = !Pxo_motd_blink_on;
|
|
Pxo_motd_blink_stamp = ui_timestamp(PXO_MOTD_BLINK_TIME);
|
|
}
|
|
|
|
// draw properly
|
|
if(Pxo_motd_blink_on){
|
|
Multi_pxo_buttons[gr_screen.res][MULTI_PXO_MOTD].button.draw_forced(2);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
// common dialog stuff ------------------------------------------------
|
|
|
|
int Multi_pxo_searching = 0;
|
|
|
|
/**
|
|
* Initialize the common dialog with the passed max input length
|
|
*/
|
|
void multi_pxo_com_init(int input_len)
|
|
{
|
|
int idx;
|
|
|
|
// create the interface window
|
|
Multi_pxo_com_window.create(0, 0, gr_screen.max_w_unscaled,gr_screen.max_h_unscaled, 0);
|
|
Multi_pxo_com_window.set_mask_bmap(Multi_pxo_com_mask_fname[gr_screen.res]);
|
|
|
|
// create the interface buttons
|
|
for(idx=0; idx<MULTI_PXO_COM_NUM_BUTTONS; idx++){
|
|
// create the object
|
|
Multi_pxo_com_buttons[gr_screen.res][idx].button.create(&Multi_pxo_com_window, "", Multi_pxo_com_buttons[gr_screen.res][idx].x, Multi_pxo_com_buttons[gr_screen.res][idx].y, 1, 1, 0, 1);
|
|
|
|
// set the sound to play when highlighted
|
|
Multi_pxo_com_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
|
|
|
|
// set the ani for the button
|
|
Multi_pxo_com_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_com_buttons[gr_screen.res][idx].filename);
|
|
|
|
// set the hotspot
|
|
Multi_pxo_com_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_com_buttons[gr_screen.res][idx].hotspot);
|
|
}
|
|
|
|
// add xstrs
|
|
for(idx=0; idx<MULTI_PXO_COM_NUM_TEXT; idx++){
|
|
Multi_pxo_com_window.add_XSTR(&Multi_pxo_com_text[gr_screen.res][idx]);
|
|
}
|
|
|
|
// create the input box
|
|
Multi_pxo_com_input.create(&Multi_pxo_com_window, Multi_pxo_com_input_coords[gr_screen.res][0], Multi_pxo_com_input_coords[gr_screen.res][1], Multi_pxo_com_input_coords[gr_screen.res][2], input_len, "", UI_INPUTBOX_FLAG_INVIS | UI_INPUTBOX_FLAG_ESC_CLR | UI_INPUTBOX_FLAG_KEYTHRU | UI_INPUTBOX_FLAG_EAT_USED);
|
|
Multi_pxo_com_input.set_focus();
|
|
|
|
// clear all text lines
|
|
memset(Multi_pxo_com_bottom_text, 0, MAX_PXO_TEXT_LEN);
|
|
memset(Multi_pxo_com_middle_text, 0, MAX_PXO_TEXT_LEN);
|
|
memset(Multi_pxo_com_top_text, 0, MAX_PXO_TEXT_LEN);
|
|
}
|
|
|
|
/**
|
|
* Close down the common dialog
|
|
*/
|
|
void multi_pxo_com_close()
|
|
{
|
|
// destroy the UI_WINDOW
|
|
Multi_pxo_com_window.destroy();
|
|
}
|
|
|
|
/**
|
|
* Blit all text lines, top, middle, bottoms
|
|
*/
|
|
void multi_pxo_com_blit_text()
|
|
{
|
|
// blit top, middle and bottom text if possible
|
|
if(Multi_pxo_com_top_text[0] != '\0'){
|
|
gr_set_color_fast(&Color_bright);
|
|
gr_string(Multi_pxo_com_top_text_coords[gr_screen.res][0], Multi_pxo_com_top_text_coords[gr_screen.res][1], Multi_pxo_com_top_text, GR_RESIZE_MENU);
|
|
}
|
|
if(Multi_pxo_com_middle_text[0] != '\0'){
|
|
gr_set_color_fast(&Color_bright);
|
|
gr_string(Multi_pxo_com_top_text_coords[gr_screen.res][0], Multi_pxo_com_middle_text_y[gr_screen.res], Multi_pxo_com_middle_text, GR_RESIZE_MENU);
|
|
}
|
|
if(Multi_pxo_com_bottom_text[0] != '\0'){
|
|
gr_set_color_fast(&Color_bright);
|
|
gr_string(Multi_pxo_com_top_text_coords[gr_screen.res][0], Multi_pxo_com_bottom_text_y[gr_screen.res], Multi_pxo_com_bottom_text, GR_RESIZE_MENU);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the top text, shortening as necessary
|
|
*/
|
|
void multi_pxo_com_set_top_text(const char *txt)
|
|
{
|
|
if((txt != NULL) && strlen(txt)){
|
|
strcpy_s(Multi_pxo_com_top_text,txt);
|
|
font::force_fit_string(Multi_pxo_com_top_text, MAX_PXO_TEXT_LEN-1, Multi_pxo_com_input_coords[gr_screen.res][2]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the middle text, shortening as necessary
|
|
*/
|
|
void multi_pxo_com_set_middle_text(const char *txt)
|
|
{
|
|
if((txt != NULL) && strlen(txt)){
|
|
strcpy_s(Multi_pxo_com_middle_text,txt);
|
|
font::force_fit_string(Multi_pxo_com_middle_text, MAX_PXO_TEXT_LEN-1, Multi_pxo_com_input_coords[gr_screen.res][2]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Set the bottom text, shortening as necessary
|
|
*/
|
|
void multi_pxo_com_set_bottom_text(const char *txt)
|
|
{
|
|
if((txt != NULL) && strlen(txt)){
|
|
strcpy_s(Multi_pxo_com_bottom_text,txt);
|
|
font::force_fit_string(Multi_pxo_com_bottom_text, MAX_PXO_TEXT_LEN-1, Multi_pxo_com_input_coords[gr_screen.res][2]);
|
|
}
|
|
}
|
|
|
|
|
|
// private channel join stuff -----------------------------------------
|
|
|
|
/**
|
|
* Initialize the popup
|
|
*/
|
|
void multi_pxo_priv_init()
|
|
{
|
|
Assert(Multi_pxo_mode != MULTI_PXO_MODE_PRIVATE);
|
|
|
|
// initialize the common dialog with the passed max input length
|
|
multi_pxo_com_init(MULTI_PXO_PRIV_MAX_TEXT_LEN);
|
|
|
|
// initialize the return code
|
|
Multi_pxo_priv_return_code = -1;
|
|
|
|
// mark us as running
|
|
Multi_pxo_mode = MULTI_PXO_MODE_PRIVATE;
|
|
|
|
// set some text
|
|
multi_pxo_com_set_middle_text(XSTR("Type the name of the channel to join/create",961));
|
|
}
|
|
|
|
/**
|
|
* Close down the popup
|
|
*/
|
|
void multi_pxo_priv_close()
|
|
{
|
|
// close down the common dialog
|
|
multi_pxo_com_close();
|
|
|
|
// mark us as not running any more
|
|
Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
|
|
}
|
|
|
|
/**
|
|
* Run the popup, 0 if still running, -1 if cancel, 1 if ok
|
|
*/
|
|
int multi_pxo_priv_popup()
|
|
{
|
|
int k;
|
|
|
|
// if we're not already running, initialize stuff
|
|
if(Multi_pxo_mode != MULTI_PXO_MODE_PRIVATE){
|
|
// intialize
|
|
multi_pxo_priv_init();
|
|
|
|
// return "still running"
|
|
return 0;
|
|
}
|
|
|
|
k = Multi_pxo_com_window.process();
|
|
|
|
// process keypresses
|
|
switch(k){
|
|
// like hitting the cancel button
|
|
case KEY_ESC:
|
|
Multi_pxo_priv_return_code = 0;
|
|
break;
|
|
}
|
|
|
|
// process button presses
|
|
multi_pxo_priv_process_buttons();
|
|
|
|
// process the inputbox
|
|
multi_pxo_priv_process_input();
|
|
|
|
// blit the background
|
|
multi_pxo_blit_all();
|
|
|
|
// blit my stuff
|
|
gr_reset_clip();
|
|
gr_set_bitmap(Multi_pxo_com_bitmap);
|
|
gr_bitmap(Multi_pxo_com_coords[gr_screen.res][0], Multi_pxo_com_coords[gr_screen.res][1], GR_RESIZE_MENU);
|
|
Multi_pxo_com_window.draw();
|
|
|
|
// blit all text lines, top, middle, bottoms
|
|
multi_pxo_com_blit_text();
|
|
|
|
gr_flip();
|
|
|
|
// check the return code
|
|
switch(Multi_pxo_priv_return_code){
|
|
// still in progress
|
|
case -1 :
|
|
return 0;
|
|
|
|
// user hit cancel
|
|
case 0 :
|
|
multi_pxo_priv_close();
|
|
return -1;
|
|
|
|
// user hit ok
|
|
case 1 :
|
|
multi_pxo_priv_close();
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Process button presses
|
|
*/
|
|
void multi_pxo_priv_process_buttons()
|
|
{
|
|
int idx;
|
|
|
|
// check all buttons
|
|
for(idx=0;idx<MULTI_PXO_COM_NUM_BUTTONS;idx++){
|
|
if(Multi_pxo_com_buttons[gr_screen.res][idx].button.pressed()){
|
|
multi_pxo_priv_button_pressed(idx);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a button press
|
|
*/
|
|
void multi_pxo_priv_button_pressed(int n)
|
|
{
|
|
char priv_chan_name[128];
|
|
|
|
switch(n){
|
|
case MULTI_PXO_COM_CANCEL:
|
|
Multi_pxo_priv_return_code = 0;
|
|
break;
|
|
|
|
case MULTI_PXO_COM_OK:
|
|
Multi_pxo_com_input.get_text(priv_chan_name);
|
|
multi_pxo_strip_space(priv_chan_name,priv_chan_name);
|
|
|
|
// if its a 0 length string, interpret as a cancel
|
|
if(strlen(priv_chan_name) <= 0){
|
|
Multi_pxo_priv_return_code = 0;
|
|
return;
|
|
}
|
|
|
|
Multi_pxo_priv_return_code = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process the inputbox
|
|
*/
|
|
void multi_pxo_priv_process_input()
|
|
{
|
|
char priv_chan_name[128];
|
|
|
|
// see if the user has pressed enter
|
|
if(Multi_pxo_com_input.pressed()){
|
|
Multi_pxo_com_input.get_text(priv_chan_name);
|
|
multi_pxo_strip_space(priv_chan_name,priv_chan_name);
|
|
|
|
// if its a 0 length string, interpret as a cancel
|
|
if(strlen(priv_chan_name) <= 0){
|
|
Multi_pxo_priv_return_code = 0;
|
|
return;
|
|
}
|
|
|
|
// otherwise interpret as "accept"
|
|
Multi_pxo_priv_return_code = 1;
|
|
|
|
// add in the "+" which indicates a private room
|
|
strcpy_s(Multi_pxo_priv_chan,"+");
|
|
strcat_s(Multi_pxo_priv_chan, priv_chan_name);
|
|
}
|
|
}
|
|
|
|
// find player stuff -----------------------------------------
|
|
|
|
char name_lookup[MAX_PXO_TEXT_LEN];
|
|
|
|
/**
|
|
* Initialize the popup
|
|
*/
|
|
void multi_pxo_find_init()
|
|
{
|
|
Assert(Multi_pxo_mode != MULTI_PXO_MODE_FIND);
|
|
|
|
// initialize the common dialog with the passed max input length
|
|
multi_pxo_com_init(MAX_PLAYER_NAME_LEN);
|
|
|
|
// return code, set to something other than -1 if we're supposed to return
|
|
Multi_pxo_find_return_code = -1;
|
|
|
|
// mark us as running
|
|
Multi_pxo_mode = MULTI_PXO_MODE_FIND;
|
|
|
|
// not searching yet
|
|
Multi_pxo_searching = 0;
|
|
|
|
// set the top text
|
|
multi_pxo_com_set_top_text(XSTR("Enter user to be found",962));
|
|
|
|
// 0 length
|
|
strcpy_s(Multi_pxo_find_channel,"");
|
|
|
|
// 0 length
|
|
strcpy_s(name_lookup,"");
|
|
}
|
|
|
|
/**
|
|
* Close down the popup
|
|
*/
|
|
void multi_pxo_find_close()
|
|
{
|
|
// close down the common dialog
|
|
multi_pxo_com_close();
|
|
|
|
// mark us as not running any more
|
|
Multi_pxo_mode = MULTI_PXO_MODE_NORMAL;
|
|
}
|
|
|
|
/**
|
|
* Run the popup, 0 if still running, -1 if cancel, 1 if ok
|
|
*/
|
|
int multi_pxo_find_popup()
|
|
{
|
|
int k;
|
|
|
|
// if we're not already running, initialize stuff
|
|
if(Multi_pxo_mode != MULTI_PXO_MODE_FIND){
|
|
// intialize
|
|
multi_pxo_find_init();
|
|
|
|
// return "still running"
|
|
return 0;
|
|
}
|
|
|
|
k = Multi_pxo_com_window.process();
|
|
|
|
// process keypresses
|
|
switch(k){
|
|
// like hitting the cancel button
|
|
case KEY_ESC:
|
|
Multi_pxo_find_return_code = 0;
|
|
break;
|
|
}
|
|
|
|
// process button presses
|
|
multi_pxo_find_process_buttons();
|
|
|
|
// process the inputbox
|
|
multi_pxo_find_process_input();
|
|
|
|
// process search mode if applicable
|
|
multi_pxo_find_search_process();
|
|
|
|
// blit the background
|
|
multi_pxo_blit_all();
|
|
|
|
// blit my stuff
|
|
gr_reset_clip();
|
|
gr_set_bitmap(Multi_pxo_com_bitmap);
|
|
gr_bitmap(Multi_pxo_com_coords[gr_screen.res][0], Multi_pxo_com_coords[gr_screen.res][1], GR_RESIZE_MENU);
|
|
Multi_pxo_com_window.draw();
|
|
|
|
// blit any text lines
|
|
multi_pxo_com_blit_text();
|
|
|
|
gr_flip();
|
|
|
|
// check the return code
|
|
switch(Multi_pxo_find_return_code){
|
|
// still in progress
|
|
case -1 :
|
|
return 0;
|
|
|
|
// user hit cancel
|
|
case 0 :
|
|
// close the popup down
|
|
multi_pxo_find_close();
|
|
return -1;
|
|
|
|
// user hit ok
|
|
case 1 :
|
|
// close the popup down
|
|
multi_pxo_find_close();
|
|
|
|
// if we have a channel, join it now if possible
|
|
if(Multi_pxo_find_channel[0] != '\0'){
|
|
pxo_channel *lookup;
|
|
lookup = multi_pxo_find_channel(Multi_pxo_find_channel);
|
|
|
|
// if we couldn't find it, don't join
|
|
if(lookup != nullptr){
|
|
multi_pxo_join_channel(lookup);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Process button presses
|
|
*/
|
|
void multi_pxo_find_process_buttons()
|
|
{
|
|
int idx;
|
|
|
|
// check all buttons
|
|
for(idx=0;idx<MULTI_PXO_COM_NUM_BUTTONS;idx++){
|
|
if(Multi_pxo_com_buttons[gr_screen.res][idx].button.pressed()){
|
|
multi_pxo_find_button_pressed(idx);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Handle a button press
|
|
*/
|
|
void multi_pxo_find_button_pressed(int n)
|
|
{
|
|
switch(n){
|
|
case MULTI_PXO_COM_CANCEL:
|
|
Multi_pxo_find_return_code = 0;
|
|
break;
|
|
|
|
case MULTI_PXO_COM_OK:
|
|
Multi_pxo_find_return_code = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process the inputbox
|
|
*/
|
|
void multi_pxo_find_process_input()
|
|
{
|
|
// see if the user has pressed enter
|
|
if(Multi_pxo_com_input.pressed()){
|
|
// if we're not already in search mode
|
|
if(!Multi_pxo_searching){
|
|
// clear all text
|
|
memset(Multi_pxo_com_middle_text,0,MAX_PXO_TEXT_LEN);
|
|
memset(Multi_pxo_com_bottom_text,0,MAX_PXO_TEXT_LEN);
|
|
|
|
Multi_pxo_com_input.get_text(name_lookup);
|
|
multi_pxo_strip_space(name_lookup,name_lookup);
|
|
|
|
// never search with a zero length string
|
|
if(name_lookup[0] != '\0'){
|
|
char search_text[512];
|
|
|
|
// put us in search mode
|
|
Multi_pxo_searching = 1;
|
|
|
|
// look for the guy
|
|
GetChannelByUser(name_lookup);
|
|
|
|
// set the top text
|
|
memset(search_text,0,512);
|
|
sprintf(search_text,XSTR("Searching for %s",963),name_lookup);
|
|
multi_pxo_com_set_top_text(search_text);
|
|
}
|
|
// clear everything
|
|
else {
|
|
memset(Multi_pxo_com_top_text,0,MAX_PXO_TEXT_LEN);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Process search mode if applicable
|
|
*/
|
|
void multi_pxo_find_search_process()
|
|
{
|
|
char *channel;
|
|
|
|
// if we're not searching for anything, return
|
|
if(!Multi_pxo_searching){
|
|
return;
|
|
}
|
|
|
|
// otherwise check to see if we've found him
|
|
channel = GetChannelByUser(NULL);
|
|
|
|
// if we've got a result, let the user know
|
|
if(channel){
|
|
// if he couldn't be found
|
|
if((ptr_s)channel == -1){
|
|
multi_pxo_com_set_middle_text(XSTR("User not found",964));
|
|
strcpy_s(Multi_pxo_find_channel,"");
|
|
} else {
|
|
if(channel[0] == '*'){
|
|
multi_pxo_com_set_middle_text(XSTR("Player is logged in but is not on a channel",965));
|
|
strcpy_s(Multi_pxo_find_channel,"");
|
|
} else {
|
|
char p_text[512];
|
|
memset(p_text,0,512);
|
|
|
|
// if this guy is on a public channel, display which one
|
|
if(channel[0] == '#'){
|
|
sprintf(p_text,XSTR("Found %s on :",966),name_lookup);
|
|
|
|
// display the results
|
|
multi_pxo_com_set_middle_text(p_text);
|
|
multi_pxo_com_set_bottom_text(channel+1);
|
|
|
|
// mark down the channel name so we know where to find him
|
|
strcpy_s(Multi_pxo_find_channel,channel);
|
|
// strip out trailing whitespace
|
|
if(Multi_pxo_find_channel[strlen(Multi_pxo_find_channel) - 1] == ' '){
|
|
Multi_pxo_find_channel[strlen(Multi_pxo_find_channel) - 1] = '\0';
|
|
}
|
|
}
|
|
// if this is a private channel
|
|
else if(channel[0] == '+'){
|
|
sprintf(p_text,XSTR("Found %s on a private channel",967),name_lookup);
|
|
multi_pxo_com_set_middle_text(p_text);
|
|
|
|
strcpy_s(Multi_pxo_find_channel,"");
|
|
}
|
|
}
|
|
}
|
|
|
|
// unset search mode
|
|
Multi_pxo_searching = 0;
|
|
|
|
// clear the inputbox
|
|
Multi_pxo_com_input.set_text("");
|
|
}
|
|
}
|
|
|
|
|
|
// player info stuff -----------------------------------------
|
|
|
|
/**
|
|
* Popup conditional functions, returns 10 on successful get of stats
|
|
*/
|
|
int multi_pxo_pinfo_cond()
|
|
{
|
|
// process common stuff
|
|
multi_pxo_process_common();
|
|
|
|
// run the networking functions for the PXO API
|
|
multi_pxo_api_process();
|
|
|
|
// process depending on what mode we're in
|
|
switch (Multi_pxo_retrieve_mode)
|
|
{
|
|
// we don't need to do anything extra here, just move on to mode 1
|
|
case 0:
|
|
{
|
|
char *ret_string;
|
|
char temp_string[MAX_PXO_TEXT_LEN];
|
|
char *tok;
|
|
|
|
// if the thing is non-null, do something
|
|
ret_string = GetTrackerIdByUser(Multi_pxo_retrieve_name);
|
|
|
|
if (ret_string != NULL) {
|
|
// user not-online/not found
|
|
if (reinterpret_cast<ptr_s>(ret_string) == -1) {
|
|
return 1;
|
|
}
|
|
|
|
// user not a tracker pilot
|
|
if ( !stricmp(ret_string,"-1") ) {
|
|
return 1;
|
|
}
|
|
|
|
// otherwise parse into his id and callsign
|
|
strcpy_s(temp_string, ret_string);
|
|
tok = strtok(temp_string, " ");
|
|
|
|
// get tracker id
|
|
if (tok != NULL) {
|
|
strcpy_s(Multi_pxo_retrieve_id, tok);
|
|
|
|
// get the callsign
|
|
tok = strtok(NULL, "");
|
|
|
|
if (tok != NULL) {
|
|
strcpy_s(Multi_pxo_retrieve_name, tok);
|
|
}
|
|
// failure
|
|
else {
|
|
return 1;
|
|
}
|
|
}
|
|
// failure of some kind or another
|
|
else {
|
|
return 1;
|
|
}
|
|
|
|
Multi_pxo_retrieve_mode = 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// initial call to get his stats
|
|
case 1:
|
|
{
|
|
// change the popup text
|
|
popup_change_text(XSTR("Getting player stats",968));
|
|
|
|
// fill in the data
|
|
memset(&Multi_pxo_pinfo, 0, sizeof(Multi_pxo_pinfo));
|
|
SDL_strlcpy(Multi_pxo_pinfo.pilot_name, Multi_pxo_retrieve_name, SDL_arraysize(Multi_pxo_pinfo.pilot_name));
|
|
Multi_pxo_pinfo.tracker_id = SDL_atoi(Multi_pxo_retrieve_id);
|
|
|
|
// make the initial call to the API
|
|
GetFSPilotData(reinterpret_cast<vmt_stats_struct*>(static_cast<uintptr_t>(0xffffffff)), nullptr, nullptr, 0);
|
|
|
|
if (GetFSPilotData(&Multi_pxo_pinfo, Multi_pxo_retrieve_name, Multi_pxo_retrieve_id, 0) != 0){
|
|
return 2;
|
|
}
|
|
// if the call went through, set the mode to 2
|
|
else {
|
|
Multi_pxo_retrieve_mode = 2;
|
|
}
|
|
|
|
break;
|
|
}
|
|
|
|
// busy retrieving his stats
|
|
case 2:
|
|
{
|
|
switch (GetFSPilotData(nullptr, nullptr, nullptr, 0)) {
|
|
// timeout, fail, cancel
|
|
case -1:
|
|
case 3:
|
|
case 2:
|
|
return 2;
|
|
|
|
// got the data
|
|
case 1:
|
|
return 10;
|
|
|
|
// still busy
|
|
case 0:
|
|
break;
|
|
}
|
|
|
|
break;
|
|
}
|
|
}
|
|
|
|
// return not done yet
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Return 1 if Multi_pxo_pinfo was successfully filled in, 0 otherwise
|
|
*/
|
|
int multi_pxo_pinfo_get(const char *name)
|
|
{
|
|
// run the popup
|
|
Multi_pxo_retrieve_mode = 0;
|
|
strcpy_s(Multi_pxo_retrieve_name, name);
|
|
|
|
switch ( popup_till_condition(multi_pxo_pinfo_cond, XSTR("&Cancel", 779), XSTR("Retrieving player tracker id", 969)) )
|
|
{
|
|
// success
|
|
case 10 :
|
|
return 1;
|
|
|
|
// failed to get his tracker id
|
|
case 1 :
|
|
return 0;
|
|
|
|
// failed to get his stats
|
|
case 2 :
|
|
return 0;
|
|
}
|
|
|
|
// we didn't get the stats
|
|
return 0;
|
|
}
|
|
|
|
bool multi_pxo_maybe_get_player(const char* name)
|
|
{
|
|
if (multi_pxo_pinfo_get(name)) {
|
|
// convert stats to player
|
|
multi_stats_tracker_to_fs(&Multi_pxo_pinfo, &Multi_pxo_pinfo_player.stats);
|
|
SDL_strlcpy(Multi_pxo_pinfo_player.callsign, Multi_pxo_pinfo.pilot_name, SDL_arraysize(Multi_pxo_pinfo_player.callsign));
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Fire up the stats view popup
|
|
*/
|
|
void multi_pxo_pinfo_show()
|
|
{
|
|
// initialize the popup
|
|
multi_pxo_pinfo_init();
|
|
|
|
// run the popup
|
|
do {
|
|
game_set_frametime(GS_STATE_PXO);
|
|
} while ( !multi_pxo_pinfo_do() );
|
|
|
|
// close down the popup
|
|
multi_pxo_pinfo_close();
|
|
}
|
|
|
|
/**
|
|
* Build the stats labels values
|
|
*/
|
|
void multi_pxo_pinfo_build_vals()
|
|
{
|
|
player *fs = &Multi_pxo_pinfo_player;
|
|
|
|
// pilot name
|
|
memset(Multi_pxo_pinfo_vals[0], 0, 50);
|
|
strcpy_s(Multi_pxo_pinfo_vals[0], fs->callsign);
|
|
font::force_fit_string(Multi_pxo_pinfo_vals[0], 49, Multi_pxo_pinfo_coords[gr_screen.res][2] - (Multi_pxo_pinfo_val_x[gr_screen.res] - Multi_pxo_pinfo_coords[gr_screen.res][0]));
|
|
|
|
// rank
|
|
memset(Multi_pxo_pinfo_vals[1], 0, 50);
|
|
multi_sg_rank_build_name(get_rank_display_name(&Ranks[verify_rank(fs->stats.rank)]).c_str(),
|
|
Multi_pxo_pinfo_vals[1]);
|
|
font::force_fit_string(Multi_pxo_pinfo_vals[1], 49, Multi_pxo_pinfo_coords[gr_screen.res][2] - (Multi_pxo_pinfo_val_x[gr_screen.res] - Multi_pxo_pinfo_coords[gr_screen.res][0]));
|
|
|
|
// kills
|
|
memset(Multi_pxo_pinfo_vals[2], 0, 50);
|
|
sprintf(Multi_pxo_pinfo_vals[2], "%d", fs->stats.kill_count);
|
|
|
|
// assists
|
|
memset(Multi_pxo_pinfo_vals[3], 0, 50);
|
|
sprintf(Multi_pxo_pinfo_vals[3], "%d", fs->stats.assists);
|
|
|
|
// friendly kills
|
|
memset(Multi_pxo_pinfo_vals[4], 0, 50);
|
|
sprintf(Multi_pxo_pinfo_vals[4], "%d", fs->stats.kill_count - fs->stats.kill_count_ok);
|
|
|
|
// missions flown
|
|
memset(Multi_pxo_pinfo_vals[5], 0, 50);
|
|
sprintf(Multi_pxo_pinfo_vals[5], "%d", (int)fs->stats.missions_flown);
|
|
|
|
// flight time
|
|
memset(Multi_pxo_pinfo_vals[6], 0, 50);
|
|
game_format_time( fl2f((float)fs->stats.flight_time), Multi_pxo_pinfo_vals[6] );
|
|
|
|
// last flown
|
|
memset(Multi_pxo_pinfo_vals[7], 0, 50);
|
|
if (fs->stats.last_flown == 0) {
|
|
strcpy_s(Multi_pxo_pinfo_vals[7], XSTR("No missions flown", 970) );
|
|
} else {
|
|
time_t tmp_lf = fs->stats.last_flown; // don't cast a pointer to a type that can be either 32 or 64bit
|
|
tm *tmr = gmtime( &tmp_lf );
|
|
|
|
if (tmr != NULL)
|
|
strftime(Multi_pxo_pinfo_vals[7], 30, "%m/%d/%y %H:%M", tmr);
|
|
else
|
|
strcpy_s(Multi_pxo_pinfo_vals[7], "");
|
|
}
|
|
|
|
// primary shots fired
|
|
memset(Multi_pxo_pinfo_vals[8], 0, 50);
|
|
sprintf(Multi_pxo_pinfo_vals[8], "%d", (int)fs->stats.p_shots_fired);
|
|
|
|
// primary shots hit
|
|
memset(Multi_pxo_pinfo_vals[9],0,50);
|
|
sprintf(Multi_pxo_pinfo_vals[9], "%d", (int)fs->stats.p_shots_hit);
|
|
|
|
// primary hit pct
|
|
memset(Multi_pxo_pinfo_vals[10], 0, 50);
|
|
if (fs->stats.p_shots_fired > 0) {
|
|
sprintf(Multi_pxo_pinfo_vals[10], "%d%%", (int)((float)fs->stats.p_shots_hit / (float)fs->stats.p_shots_fired * 100.0f));
|
|
} else {
|
|
strcpy_s(Multi_pxo_pinfo_vals[10], "0%");
|
|
}
|
|
|
|
// secondary shots fired
|
|
memset(Multi_pxo_pinfo_vals[11], 0, 50);
|
|
sprintf(Multi_pxo_pinfo_vals[11], "%d", (int)fs->stats.s_shots_fired);
|
|
|
|
// secondary shots hit
|
|
memset(Multi_pxo_pinfo_vals[12], 0, 50);
|
|
sprintf(Multi_pxo_pinfo_vals[12], "%d", (int)fs->stats.s_shots_hit);
|
|
|
|
// secondary hit pct
|
|
memset(Multi_pxo_pinfo_vals[13], 0, 50);
|
|
if (fs->stats.s_shots_fired > 0) {
|
|
sprintf(Multi_pxo_pinfo_vals[13], "%d%%", (int)((float)fs->stats.s_shots_hit / (float)fs->stats.s_shots_fired * 100.0f));
|
|
} else {
|
|
strcpy_s(Multi_pxo_pinfo_vals[13], "0%");
|
|
}
|
|
|
|
// primary friendly hits
|
|
memset(Multi_pxo_pinfo_vals[14], 0, 50);
|
|
sprintf(Multi_pxo_pinfo_vals[14], "%u", fs->stats.p_bonehead_hits);
|
|
|
|
// primary friendly hit %
|
|
memset(Multi_pxo_pinfo_vals[15], 0, 50);
|
|
if (fs->stats.p_shots_fired > 0) {
|
|
sprintf(Multi_pxo_pinfo_vals[15], "%d%%", (int)((float)100.0f*((float)fs->stats.p_bonehead_hits/(float)fs->stats.p_shots_fired)));
|
|
} else {
|
|
strcpy_s(Multi_pxo_pinfo_vals[15], "0%");
|
|
}
|
|
|
|
// secondary friendly hits
|
|
memset(Multi_pxo_pinfo_vals[16], 0, 50);
|
|
sprintf(Multi_pxo_pinfo_vals[16], "%u", fs->stats.s_bonehead_hits);
|
|
|
|
// secondary friendly hit %
|
|
memset(Multi_pxo_pinfo_vals[17], 0, 50);
|
|
if (fs->stats.s_shots_fired > 0) {
|
|
sprintf(Multi_pxo_pinfo_vals[17], "%d%%", (int)((float)100.0f*((float)fs->stats.s_bonehead_hits/(float)fs->stats.s_shots_fired)));
|
|
} else {
|
|
strcpy_s(Multi_pxo_pinfo_vals[17], "0%");
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Initialize the popup
|
|
*/
|
|
void multi_pxo_pinfo_init()
|
|
{
|
|
int idx;
|
|
|
|
// create the interface window
|
|
Multi_pxo_pinfo_window.create(0,0,gr_screen.max_w_unscaled,gr_screen.max_h_unscaled,0);
|
|
Multi_pxo_pinfo_window.set_mask_bmap(Multi_pxo_pinfo_mask_fname[gr_screen.res]);
|
|
|
|
Multi_pxo_pinfo_bitmap = bm_load(Multi_pxo_pinfo_fname[gr_screen.res]);
|
|
Assert(Multi_pxo_pinfo_bitmap != -1);
|
|
|
|
// create the interface buttons
|
|
for(idx=0; idx<MULTI_PXO_PINFO_NUM_BUTTONS; idx++){
|
|
// create the object
|
|
Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.create(&Multi_pxo_pinfo_window, "", Multi_pxo_pinfo_buttons[gr_screen.res][idx].x, Multi_pxo_pinfo_buttons[gr_screen.res][idx].y, 1, 1, 0, 1);
|
|
|
|
// set the sound to play when highlighted
|
|
Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
|
|
|
|
// set the ani for the button
|
|
Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_pinfo_buttons[gr_screen.res][idx].filename);
|
|
|
|
// set the hotspot
|
|
Multi_pxo_pinfo_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_pinfo_buttons[gr_screen.res][idx].hotspot);
|
|
}
|
|
|
|
// add xstrs
|
|
for(idx=0; idx<MULTI_PXO_PINFO_NUM_TEXT; idx++){
|
|
Multi_pxo_pinfo_window.add_XSTR(&Multi_pxo_pinfo_text[gr_screen.res][idx]);
|
|
}
|
|
|
|
// set up the stats labels
|
|
Multi_pxo_pinfo_stats_labels[0] = vm_strdup(XSTR("Name", 1532));
|
|
Multi_pxo_pinfo_stats_labels[1] = vm_strdup(XSTR("Rank", 1533));
|
|
Multi_pxo_pinfo_stats_labels[2] = vm_strdup(XSTR("Kills", 1534));
|
|
Multi_pxo_pinfo_stats_labels[3] = vm_strdup(XSTR("Assists", 1535));
|
|
Multi_pxo_pinfo_stats_labels[4] = vm_strdup(XSTR("Friendly kills", 1536));
|
|
Multi_pxo_pinfo_stats_labels[5] = vm_strdup(XSTR("Missions flown", 1537));
|
|
Multi_pxo_pinfo_stats_labels[6] = vm_strdup(XSTR("Flight time", 1538));
|
|
Multi_pxo_pinfo_stats_labels[7] = vm_strdup(XSTR("Last flown", 1539));
|
|
Multi_pxo_pinfo_stats_labels[8] = vm_strdup(XSTR("Primary shots fired", 1540));
|
|
Multi_pxo_pinfo_stats_labels[9] = vm_strdup(XSTR("Primary shots hit", 1541));
|
|
Multi_pxo_pinfo_stats_labels[10] = vm_strdup(XSTR("Primary hit %", 1542));
|
|
Multi_pxo_pinfo_stats_labels[11] = vm_strdup(XSTR("Secondary shots fired", 1543));
|
|
Multi_pxo_pinfo_stats_labels[12] = vm_strdup(XSTR("Secondary shots hit", 1544));
|
|
Multi_pxo_pinfo_stats_labels[13] = vm_strdup(XSTR("Secondary hit %", 1545));
|
|
Multi_pxo_pinfo_stats_labels[14] = vm_strdup(XSTR("Primary friendly hits", 1546));
|
|
Multi_pxo_pinfo_stats_labels[15] = vm_strdup(XSTR("Primary friendly hit %", 1547));
|
|
Multi_pxo_pinfo_stats_labels[16] = vm_strdup(XSTR("Secondary friendly hits", 1548));
|
|
Multi_pxo_pinfo_stats_labels[17] = vm_strdup(XSTR("Secondary friendly hit %", 1549));
|
|
|
|
// build the stats labels values
|
|
multi_pxo_pinfo_build_vals();
|
|
}
|
|
|
|
/**
|
|
* Do frame
|
|
*/
|
|
int multi_pxo_pinfo_do()
|
|
{
|
|
int k = Multi_pxo_pinfo_window.process();
|
|
|
|
// process common stuff
|
|
multi_pxo_process_common();
|
|
|
|
// run the networking functions for the PXO API
|
|
multi_pxo_api_process();
|
|
|
|
// check to see if he pressed escp
|
|
if(k == KEY_ESC){
|
|
return 1;
|
|
}
|
|
|
|
// if he pressed the ok button
|
|
if(Multi_pxo_pinfo_buttons[gr_screen.res][MULTI_PXO_PINFO_OK].button.pressed()){
|
|
return 1;
|
|
}
|
|
|
|
// if he pressed the medals buttons, run the medals screen
|
|
if(Multi_pxo_pinfo_buttons[gr_screen.res][MULTI_PXO_PINFO_MEDALS].button.pressed()){
|
|
multi_pxo_run_medals();
|
|
}
|
|
|
|
// draw stuff
|
|
|
|
// blit everything on the "normal" screen
|
|
multi_pxo_blit_all();
|
|
|
|
// blit our own stuff
|
|
gr_reset_clip();
|
|
gr_set_bitmap(Multi_pxo_pinfo_bitmap);
|
|
gr_bitmap(0, 0, GR_RESIZE_MENU);
|
|
Multi_pxo_pinfo_window.draw();
|
|
|
|
// blit the stats themselves
|
|
multi_pxo_pinfo_blit();
|
|
|
|
// flip the page
|
|
gr_flip();
|
|
|
|
// not done yet
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* Close
|
|
*/
|
|
void multi_pxo_pinfo_close()
|
|
{
|
|
int i;
|
|
|
|
// destroy the UI_WINDOW
|
|
Multi_pxo_pinfo_window.destroy();
|
|
|
|
// unload the bitmap
|
|
if(Multi_pxo_pinfo_bitmap != -1){
|
|
bm_release(Multi_pxo_pinfo_bitmap);
|
|
}
|
|
|
|
// free the stats labels strings
|
|
for (i=0; i<MULTI_PXO_PINFO_NUM_LABELS; i++) {
|
|
vm_free(Multi_pxo_pinfo_stats_labels[i]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Blit all the stats on this screen
|
|
*/
|
|
void multi_pxo_pinfo_blit()
|
|
{
|
|
int idx;
|
|
int y_start;
|
|
|
|
// blit all the labels
|
|
y_start = Multi_pxo_pinfo_coords[gr_screen.res][1];
|
|
for(idx=0; idx<MULTI_PXO_PINFO_NUM_LABELS; idx++){
|
|
// blit the label
|
|
gr_set_color_fast(&Color_bright);
|
|
gr_string(Multi_pxo_pinfo_coords[gr_screen.res][0], y_start, Multi_pxo_pinfo_stats_labels[idx], GR_RESIZE_MENU);
|
|
|
|
// blit the label's value
|
|
gr_set_color_fast(&Color_normal);
|
|
gr_string(Multi_pxo_pinfo_val_x[gr_screen.res], y_start, Multi_pxo_pinfo_vals[idx], GR_RESIZE_MENU);
|
|
|
|
// spacing
|
|
y_start += Multi_pxo_pinfo_stats_spacing[idx];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Run the medals screen
|
|
*/
|
|
void multi_pxo_run_medals()
|
|
{
|
|
int ret_code;
|
|
|
|
// process common stuff
|
|
multi_pxo_process_common();
|
|
|
|
// run the networking functions for the PXO API
|
|
multi_pxo_api_process();
|
|
|
|
// initialize the freespace data and the player struct
|
|
multi_stats_tracker_to_fs(&Multi_pxo_pinfo, &Multi_pxo_pinfo_player.stats);
|
|
SDL_strlcpy(Multi_pxo_pinfo_player.callsign, Multi_pxo_pinfo.pilot_name, SDL_arraysize(Multi_pxo_pinfo_player.callsign));
|
|
|
|
// initialize the medals screen
|
|
medal_main_init(&Multi_pxo_pinfo_player, MM_POPUP);
|
|
|
|
// run the medals screen until it says that it should be closed
|
|
do {
|
|
// set frametime and run common functions
|
|
game_set_frametime(-1);
|
|
game_do_state_common( gameseq_get_state() );
|
|
|
|
// run the medals screen
|
|
ret_code = medal_main_do();
|
|
} while(ret_code);
|
|
|
|
// close the medals screen down
|
|
medal_main_close();
|
|
}
|
|
|
|
|
|
// notify stuff stuff -----------------------------------------
|
|
|
|
/**
|
|
* Add a notification string
|
|
*/
|
|
void multi_pxo_notify_add(const char *txt)
|
|
{
|
|
// copy the text
|
|
strcpy_s(Multi_pxo_notify_text, txt);
|
|
|
|
// set the timestamp
|
|
Multi_pxo_notify_stamp = ui_timestamp(MULTI_PXO_NOTIFY_TIME);
|
|
}
|
|
|
|
/**
|
|
* Blit and process the notification string
|
|
*/
|
|
void multi_pxo_notify_blit()
|
|
{
|
|
int w;
|
|
|
|
// if the timestamp is -1, do nothing
|
|
if (!Multi_pxo_notify_stamp.isValid()){
|
|
return;
|
|
}
|
|
|
|
// if it has expired, do nothing
|
|
if (ui_timestamp_elapsed(Multi_pxo_notify_stamp)){
|
|
Multi_pxo_notify_stamp = UI_TIMESTAMP::invalid();
|
|
}
|
|
|
|
// otherwise blit the text
|
|
gr_set_color_fast(&Color_bright);
|
|
gr_get_string_size(&w,NULL,Multi_pxo_notify_text);
|
|
gr_string((gr_screen.max_w_unscaled - w)/2,MULTI_PXO_NOTIFY_Y,Multi_pxo_notify_text,GR_RESIZE_MENU);
|
|
}
|
|
|
|
|
|
/**
|
|
* Initialize the PXO help screen
|
|
*/
|
|
void multi_pxo_help_init()
|
|
{
|
|
int idx;
|
|
|
|
// load the background bitmap
|
|
Multi_pxo_help_bitmap = bm_load(Multi_pxo_help_fname[gr_screen.res]);
|
|
if(Multi_pxo_help_bitmap < 0){
|
|
// we failed to load the bitmap - this is very bad
|
|
Int3();
|
|
}
|
|
|
|
// create the interface window
|
|
Multi_pxo_help_window.create(0,0,gr_screen.max_w_unscaled,gr_screen.max_h_unscaled,0);
|
|
Multi_pxo_help_window.set_mask_bmap(Multi_pxo_help_mask_fname[gr_screen.res]);
|
|
|
|
// create the interface buttons
|
|
for(idx=0; idx<MULTI_PXO_HELP_NUM_BUTTONS; idx++){
|
|
// create the object
|
|
Multi_pxo_help_buttons[gr_screen.res][idx].button.create(&Multi_pxo_help_window, "", Multi_pxo_help_buttons[gr_screen.res][idx].x, Multi_pxo_help_buttons[gr_screen.res][idx].y, 1, 1, 0, 1);
|
|
|
|
// set the sound to play when highlighted
|
|
Multi_pxo_help_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);
|
|
|
|
// set the ani for the button
|
|
Multi_pxo_help_buttons[gr_screen.res][idx].button.set_bmaps(Multi_pxo_help_buttons[gr_screen.res][idx].filename);
|
|
|
|
// set the hotspot
|
|
Multi_pxo_help_buttons[gr_screen.res][idx].button.link_hotspot(Multi_pxo_help_buttons[gr_screen.res][idx].hotspot);
|
|
}
|
|
|
|
// add xstrs
|
|
for(idx=0; idx<MULTI_PXO_HELP_NUM_TEXT; idx++){
|
|
Multi_pxo_help_window.add_XSTR(&Multi_pxo_help_text[gr_screen.res][idx]);
|
|
}
|
|
|
|
multi_pxo_help_load();
|
|
|
|
// set the current page to 0
|
|
Multi_pxo_help_cur = 0;
|
|
}
|
|
|
|
/**
|
|
* Do frame for PXO help
|
|
*/
|
|
void multi_pxo_help_do()
|
|
{
|
|
// run api stuff
|
|
if(Multi_pxo_connected){
|
|
multi_pxo_api_process();
|
|
}
|
|
|
|
// process common stuff
|
|
multi_pxo_process_common();
|
|
|
|
int k = Multi_pxo_help_window.process();
|
|
|
|
// process any keypresses
|
|
switch(k){
|
|
case KEY_ESC:
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
gameseq_post_event(GS_EVENT_PXO);
|
|
break;
|
|
}
|
|
|
|
// process button presses
|
|
multi_pxo_help_process_buttons();
|
|
|
|
// draw the background, etc
|
|
gr_reset_clip();
|
|
GR_MAYBE_CLEAR_RES(Multi_pxo_help_bitmap);
|
|
if(Multi_pxo_help_bitmap != -1){
|
|
gr_set_bitmap(Multi_pxo_help_bitmap);
|
|
gr_bitmap(0,0,GR_RESIZE_MENU);
|
|
}
|
|
Multi_pxo_help_window.draw();
|
|
|
|
// blit the current page
|
|
multi_pxo_help_blit_page();
|
|
|
|
// page flip
|
|
gr_flip();
|
|
}
|
|
|
|
/**
|
|
* Close the pxo screen
|
|
*/
|
|
void multi_pxo_help_close()
|
|
{
|
|
|
|
// unload any bitmaps
|
|
bm_release(Multi_pxo_help_bitmap);
|
|
|
|
// destroy the UI_WINDOW
|
|
Multi_pxo_help_window.destroy();
|
|
|
|
multi_pxo_help_free();
|
|
}
|
|
|
|
/**
|
|
* Load the help file up
|
|
*/
|
|
void multi_pxo_help_free()
|
|
{
|
|
// free all pages
|
|
for (int idx = 0; idx < Multi_pxo_help_num_pages; idx++) {
|
|
for (int idx2 = 0; idx2 < Multi_pxo_help_pages[idx].num_lines; idx2++) {
|
|
// maybe free
|
|
if (Multi_pxo_help_pages[idx].text[idx2] != NULL) {
|
|
vm_free(Multi_pxo_help_pages[idx].text[idx2]);
|
|
Multi_pxo_help_pages[idx].text[idx2] = NULL;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load the help file up
|
|
*/
|
|
void multi_pxo_help_load()
|
|
{
|
|
CFILE *in;
|
|
help_page *cp;
|
|
|
|
// read in the text file
|
|
in = NULL;
|
|
in = cfopen(MULTI_PXO_HELP_FILE,"rt",CF_TYPE_DATA);
|
|
Assert(in != NULL);
|
|
if(in == NULL){
|
|
return;
|
|
}
|
|
|
|
Multi_pxo_help_num_pages = 0;
|
|
|
|
// blast all the help pages clear
|
|
memset(Multi_pxo_help_pages, 0, sizeof(help_page) * MULTI_PXO_MAX_PAGES);
|
|
Multi_pxo_help_num_pages = 0;
|
|
cp = &Multi_pxo_help_pages[0];
|
|
|
|
while(!cfeof(in)){
|
|
// malloc the line
|
|
cp->text[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;idx<cp->num_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;idx<MULTI_PXO_HELP_NUM_BUTTONS;idx++){
|
|
if(Multi_pxo_help_buttons[gr_screen.res][idx].button.pressed()){
|
|
multi_pxo_help_button_pressed(idx);
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Button pressed
|
|
*/
|
|
void multi_pxo_help_button_pressed(int n)
|
|
{
|
|
switch(n){
|
|
case MULTI_PXO_HELP_PREV:
|
|
// if we're already at page 0, do nothing
|
|
if(Multi_pxo_help_cur == 0){
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
} else {
|
|
Multi_pxo_help_cur--;
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
}
|
|
break;
|
|
|
|
case MULTI_PXO_HELP_NEXT:
|
|
// if we're already at max pages, do nothing
|
|
if(Multi_pxo_help_cur == Multi_pxo_help_num_pages){
|
|
gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
|
|
} else {
|
|
Multi_pxo_help_cur++;
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
}
|
|
break;
|
|
|
|
case MULTI_PXO_HELP_CONTINUE:
|
|
gamesnd_play_iface(InterfaceSounds::USER_SELECT);
|
|
gameseq_post_event(GS_EVENT_PXO);
|
|
break;
|
|
}
|
|
}
|
|
|
|
// http banner stuff ---------------------------------------------
|
|
|
|
/**
|
|
* Initialisation
|
|
*/
|
|
void multi_pxo_ban_init()
|
|
{
|
|
// zero the active banner bitmap
|
|
Multi_pxo_banner.ban_bitmap = -1;
|
|
|
|
Multi_pxo_ban_swap_stamp = UI_TIMESTAMP::invalid();
|
|
|
|
// are we doing banners at all?
|
|
if ( os_config_read_uint(nullptr, "PXOBanners", 1) && strlen(Multi_options_g.pxo_banner_url) ) {
|
|
// if we're already in idle mode, we're done downloading for this instance of freespace. pick a random image we already have
|
|
if(Multi_pxo_ban_mode == PXO_BAN_MODE_IDLE){
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_CHOOSE_RANDOM;
|
|
return;
|
|
}
|
|
|
|
// set ourselves to startup mode
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_LIST_STARTUP;
|
|
Multi_pxo_ban_get = NULL;
|
|
} else {
|
|
// set ourselves to idle mode
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
|
|
Multi_pxo_ban_get = NULL;
|
|
}
|
|
|
|
// zero the active banner bitmap
|
|
Multi_pxo_banner.ban_bitmap = -1;
|
|
Multi_pxo_banner.ban_file.clear();
|
|
Multi_pxo_banner.ban_file_url.clear();
|
|
Multi_pxo_banner.ban_url.clear();
|
|
}
|
|
|
|
/**
|
|
* Process http download details
|
|
*/
|
|
void multi_pxo_ban_process(bool api_access)
|
|
{
|
|
SCP_string url_string;
|
|
SCP_string local_file;
|
|
|
|
// if they've been here long enough, maybe swap out the banner
|
|
if ((Multi_pxo_ban_mode == PXO_BAN_MODE_IDLE) && ui_timestamp_elapsed(Multi_pxo_ban_swap_stamp)) {
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_STARTUP;
|
|
Multi_pxo_ban_swap_stamp = UI_TIMESTAMP::invalid();
|
|
}
|
|
|
|
// process stuff
|
|
switch(Multi_pxo_ban_mode){
|
|
// start downloading list
|
|
case PXO_BAN_MODE_LIST_STARTUP:
|
|
// remote file
|
|
url_string = Multi_options_g.pxo_banner_url;
|
|
|
|
if (url_string.back() != '/') {
|
|
url_string.push_back('/');
|
|
}
|
|
|
|
url_string += PXO_BANNERS_CONFIG_FILE;
|
|
|
|
// add game tag if we've got one (for mod-specific banner config)
|
|
if ( !Multi_fs_tracker_game_tag.empty() ) {
|
|
url_string += "?tag=" + Multi_fs_tracker_game_tag;
|
|
}
|
|
|
|
// local file
|
|
cf_create_default_path_string(local_file, CF_TYPE_MULTI_CACHE, PXO_BANNERS_CONFIG_FILE);
|
|
|
|
// try creating the file get object
|
|
Multi_pxo_ban_get = new InetGetFile(url_string.c_str(), local_file.c_str(), CF_TYPE_MULTI_CACHE);
|
|
|
|
// bad
|
|
if (Multi_pxo_ban_get == NULL) {
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
|
|
break;
|
|
}
|
|
|
|
// go to the downloading list mode
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_LIST;
|
|
break;
|
|
|
|
// downloading list
|
|
case PXO_BAN_MODE_LIST:
|
|
// 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_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();
|
|
|
|
// if we have no active file, we're done
|
|
if ( Multi_pxo_banner.ban_file.empty() || Multi_pxo_banner.ban_file_url.empty() ) {
|
|
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.c_str(), 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, CF_TYPE_MULTI_CACHE, Multi_pxo_banner.ban_file.c_str());
|
|
|
|
// try creating the file get object
|
|
Multi_pxo_ban_get = new InetGetFile(Multi_pxo_banner.ban_file_url.c_str(), local_file.c_str(), CF_TYPE_MULTI_CACHE);
|
|
|
|
// bad
|
|
if (Multi_pxo_ban_get == NULL) {
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
|
|
break;
|
|
}
|
|
|
|
// go to the downloading images mode
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES;
|
|
break;
|
|
|
|
// downloading files
|
|
case PXO_BAN_MODE_IMAGES:
|
|
// error
|
|
if ( Multi_pxo_ban_get->IsFileError() ) {
|
|
delete Multi_pxo_ban_get;
|
|
Multi_pxo_ban_get = NULL;
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_IDLE;
|
|
break;
|
|
}
|
|
|
|
// connecting, receiving
|
|
if ( Multi_pxo_ban_get->IsConnecting() || Multi_pxo_ban_get->IsReceiving() )
|
|
break;
|
|
|
|
// done!
|
|
if ( Multi_pxo_ban_get->IsFileReceived() ) {
|
|
delete Multi_pxo_ban_get;
|
|
Multi_pxo_ban_get = NULL;
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_DONE;
|
|
}
|
|
break;
|
|
|
|
// done downloading - maybe load an image
|
|
case PXO_BAN_MODE_IMAGES_DONE:
|
|
// make sure we have a valid filename
|
|
if ( !Multi_pxo_banner.ban_file.empty() ) {
|
|
// if we have a loaded bitmap, unload it
|
|
if (Multi_pxo_banner.ban_bitmap != -1) {
|
|
bm_release(Multi_pxo_banner.ban_bitmap);
|
|
}
|
|
|
|
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 ( !api_access && Multi_pxo_ban_button.pressed() ) {
|
|
multi_pxo_ban_clicked();
|
|
}
|
|
|
|
// set time to choose a new banner to show
|
|
if ( !Multi_pxo_ban_swap_stamp.isValid() ) {
|
|
Multi_pxo_ban_swap_stamp = ui_timestamp(PXO_BANNER_SWAP_TIME);
|
|
}
|
|
break;
|
|
|
|
case PXO_BAN_MODE_CHOOSE_RANDOM:
|
|
// just bounce back to IMAGES_STARTUP, which will pick a new banner
|
|
// image and download it if necessary
|
|
Multi_pxo_ban_mode = PXO_BAN_MODE_IMAGES_STARTUP;
|
|
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_release(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()
|
|
{
|
|
char file_url[512] = "";
|
|
char banners[10][512];
|
|
char urls[10][512];
|
|
int num_banners, idx;
|
|
|
|
CFILE *in = cfopen(PXO_BANNERS_CONFIG_FILE, "rt", CF_TYPE_MULTI_CACHE);
|
|
|
|
// bad
|
|
if(in == NULL){
|
|
return;
|
|
}
|
|
|
|
// clear all strings
|
|
for(idx=0; idx<10; idx++){
|
|
strcpy_s(banners[idx], "");
|
|
strcpy_s(urls[idx], "");
|
|
}
|
|
|
|
// get the global banner url
|
|
if(cfgets(file_url, 254, in) == NULL){
|
|
cfclose(in);
|
|
cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE);
|
|
return;
|
|
}
|
|
drop_leading_white_space(file_url);
|
|
drop_trailing_white_space(file_url);
|
|
|
|
// verify that it's a proper url
|
|
if ( strncmp(file_url, "http://", 7) && strncmp(file_url, "ftp://", 6) ) {
|
|
cfclose(in);
|
|
cf_delete(PXO_BANNERS_CONFIG_FILE, CF_TYPE_MULTI_CACHE);
|
|
return;
|
|
}
|
|
|
|
// otherwise read in
|
|
num_banners = 0;
|
|
while(num_banners < 10){
|
|
// try and get the pcx
|
|
if(cfgets(banners[num_banners], 254, in) == NULL){
|
|
break;
|
|
}
|
|
// try and get the url
|
|
if(cfgets(urls[num_banners], 254, in) == NULL){
|
|
break;
|
|
}
|
|
|
|
// strip off trailing and leading whitespace
|
|
drop_leading_white_space(banners[num_banners]);
|
|
drop_trailing_white_space(banners[num_banners]);
|
|
drop_leading_white_space(urls[num_banners]);
|
|
drop_trailing_white_space(urls[num_banners]);
|
|
|
|
// got one
|
|
num_banners++;
|
|
}
|
|
|
|
// close the file
|
|
cfclose(in);
|
|
|
|
// no banners
|
|
if(num_banners <= 0){
|
|
return;
|
|
}
|
|
|
|
idx = 0;
|
|
|
|
if (num_banners > 1) {
|
|
do {
|
|
// randomly pick a file for download
|
|
idx = Random::next(num_banners);
|
|
} while (Multi_pxo_banner.ban_file == banners[idx]);
|
|
}
|
|
|
|
// base filename
|
|
Multi_pxo_banner.ban_file = banners[idx];
|
|
|
|
// get the full file url
|
|
Multi_pxo_banner.ban_file_url = file_url;
|
|
|
|
if (Multi_pxo_banner.ban_file_url.back() != '/') {
|
|
Multi_pxo_banner.ban_file_url.push_back('/');
|
|
}
|
|
|
|
Multi_pxo_banner.ban_file_url.append(banners[idx]);
|
|
|
|
// url of where to go to when clicked
|
|
Multi_pxo_banner.ban_url = urls[idx];
|
|
}
|
|
|
|
/**
|
|
* Any bitmap or info or whatever
|
|
*/
|
|
void multi_pxo_ban_draw()
|
|
{
|
|
// if we have a valid bitmap
|
|
if(Multi_pxo_banner.ban_bitmap >= 0){
|
|
// if the mouse is over the banner button, highlight with a rectangle
|
|
if (Multi_pxo_ban_button.is_mouse_on() && !Multi_pxo_banner.ban_url.empty()) {
|
|
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.empty() ) {
|
|
multi_pxo_url(Multi_pxo_banner.ban_url.c_str());
|
|
}
|
|
}
|