Files
fs2open.github.com/code/network/multi_pxo.cpp
T
Taylor Richards 29ce72f66f switch to using winsock2
also removes some obsolete win32 #ifdef's and
cleans up some windows #include's
2019-11-15 23:33:35 -05:00

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