/*
 * Copyright (C) Volition, Inc. 1999.  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.
 *
*/




#include "network/multiteamselect.h"
#include "gamesequence/gamesequence.h"
#include "gamesnd/gamesnd.h"
#include "globalincs/alphacolors.h"
#include "globalincs/linklist.h"
#include "graphics/font.h"
#include "io/key.h"
#include "io/mouse.h"
#include "menuui/snazzyui.h"
#include "mission/missionparse.h"
#include "missionui/chatbox.h"
#include "missionui/missionscreencommon.h"
#include "missionui/missionshipchoice.h"
#include "missionui/missionweaponchoice.h"
#include "missionui/missionbrief.h"
#include "network/multi.h"
#include "network/multi_endgame.h"
#include "network/multimsgs.h"
#include "network/multiui.h"
#include "network/multiutil.h"
#include "object/object.h"
#include "parse/parselo.h"
#include "playerman/player.h"
#include "popup/popup.h"
#include "ship/ship.h"
#include "utils/string_utils.h"
#include "weapon/weapon.h"


// ------------------------------------------------------------------------------------------------------
// TEAM SELECT DEFINES/VARS
//

// mission screen common data
extern int Next_screen;

//XSTR:OFF

// bitmap defines
#define MULTI_TS_PALETTE							"InterfacePalette"

const char *Multi_ts_bitmap_fname[GR_NUM_RESOLUTIONS] = {
	"TeamSelect",		// GR_640
	"2_TeamSelect"		// GR_1024
};

const char *Multi_ts_bitmap_mask_fname[GR_NUM_RESOLUTIONS] = {
	"TeamSelect-M",	// GR_640
	"2_TeamSelect-M"		// GR_1024
};

// constants for coordinate lookup
#define MULTI_TS_X_COORD 0
#define MULTI_TS_Y_COORD 1
#define MULTI_TS_W_COORD 2
#define MULTI_TS_H_COORD 3

#define MULTI_TS_NUM_BUTTONS						7
#define MULTI_TS_BRIEFING							0					// go to the briefing
#define MULTI_TS_SHIP_SELECT						1					// this screen
#define MULTI_TS_WEAPON_SELECT					2					// go to the weapon select screen
#define MULTI_TS_SHIPS_UP							3					// scroll the ships list up
#define MULTI_TS_SHIPS_DOWN						4					// scroll the ships list down
#define MULTI_TS_COMMIT								5					// commit
#define MULTI_TS_LOCK								6					// lock (free) ship/weapon select

ui_button_info Multi_ts_buttons[GR_NUM_RESOLUTIONS][MULTI_TS_NUM_BUTTONS] = {
	{ // GR_640
		ui_button_info("CB_00",	7,		3,		37,	7,		0),
		ui_button_info("CB_01",	7,		19,	37,	23,	1),
		ui_button_info("CB_02",	7,		35,	37,	39,	2),
		ui_button_info("TSB_03",	5,		303,	-1,	-1,	3),
		ui_button_info("TSB_04",	5,		454,	-1,	-1,	4),
		ui_button_info("TSB_09",	571,	425,	572,	413,	9),
		ui_button_info("TSB_34",	603,	374,	602,	364,	34)
	},
	{ // GR_1024

		ui_button_info("2_CB_00",	12,	5,		59,	12,	0),
		ui_button_info("2_CB_01",	12,	31,	59,	37,	1),
		ui_button_info("2_CB_02",	12,	56,	59,	62,	2),
		ui_button_info("2_TSB_03",	8,		485,	-1,	-1,	3),
		ui_button_info("2_TSB_04",	8,		727,	-1,	-1,	4),
		ui_button_info("2_TSB_09",	914,	681,	937,	660,	9),
		ui_button_info("2_TSB_34",	966,	599,	964,	584,	34)
	},
};


// players locked ani graphic
#define MULTI_TS_NUM_LOCKED_BITMAPS				3

const char *Multi_ts_bmap_names[GR_NUM_RESOLUTIONS][3] = {
	{ // GR_640
		"TSB_340000",
		"TSB_340001",
		"TSB_340002"
	}, 
	{ // GR_1024
		"2_TSB_340000",
		"2_TSB_340001",
		"2_TSB_340002"
	}
};
int Multi_ts_locked_bitmaps[MULTI_TS_NUM_LOCKED_BITMAPS];


// snazzy menu regions
#define TSWING_0_SHIP_0								10
#define TSWING_0_SHIP_1								12
#define TSWING_0_SHIP_2								14
#define TSWING_0_SHIP_3								16
#define TSWING_1_SHIP_0								18
#define TSWING_1_SHIP_1								20
#define TSWING_1_SHIP_2								22
#define TSWING_1_SHIP_3								24
#define TSWING_2_SHIP_0								26
#define TSWING_2_SHIP_1								28
#define TSWING_2_SHIP_2								30
#define TSWING_2_SHIP_3								32

#define TSWING_0_NAME_0								11
#define TSWING_0_NAME_1								13
#define TSWING_0_NAME_2								15
#define TSWING_0_NAME_3								17
#define TSWING_1_NAME_0								19
#define TSWING_1_NAME_1								21
#define TSWING_1_NAME_2								23
#define TSWING_1_NAME_3								25
#define TSWING_2_NAME_0								27
#define TSWING_2_NAME_1								29
#define TSWING_2_NAME_2								31
#define TSWING_2_NAME_3								33

#define TSWING_LIST_0								5
#define TSWING_LIST_1								6
#define TSWING_LIST_2								7
#define TSWING_LIST_3								8

#define MULTI_TS_SLOT_LIST							0
#define MULTI_TS_PLAYER_LIST						1
#define MULTI_TS_AVAIL_LIST						2
	
// interface data
#define MULTI_TS_NUM_SNAZZY_REGIONS				28
int Multi_ts_bitmap;
int Multi_ts_mask;
int Multi_ts_inited = 0;
int Multi_ts_snazzy_regions;
ubyte *Multi_ts_mask_data;	
int Multi_ts_mask_w, Multi_ts_mask_h;
std::array<MENU_REGION, MULTI_TS_NUM_SNAZZY_REGIONS> Multi_ts_region;
UI_WINDOW Multi_ts_window;

// ship slot data
#define MULTI_TS_NUM_SHIP_SLOTS_TEAM	4														// # of ship slots in team v team
#define MULTI_TS_FLAG_NONE					-2														// never has any ships
#define MULTI_TS_FLAG_EMPTY				-1														// currently empty

static int Multi_ts_slot_icon_coords[MULTI_TS_NUM_SHIP_SLOTS][GR_NUM_RESOLUTIONS][2] = {							// x,y
	{		// 	
			{128,301},		// GR_640
			{205,482}		// GR_1024
	},
	{		// 
			{91,347},		// GR_640
			{146,555}		// GR_1024
	},
	{		// 
			{166,347},		// GR_640
			{266,555}		// GR_1024
	},
	{		// alpha
			{128,395},		// GR_640
			{205,632}		// GR_1024
	},
	{		//  
			{290,301},		// GR_640
			{464,482}		// GR_1024
	},
	{		// 
			{253,347},		// GR_640
			{405,555}		// GR_1024
	},
	{		// 
			{328,347},		// GR_640
			{525,555}		// GR_1024
	},
	{		// beta
			{290,395},		// GR_640
			{464,632}		// GR_1024
	},
	{		// 
			{453,301},		// GR_640
			{725,482}		// GR_1024
	},
	{		// 
			{416,347},		// GR_640
			{666,555}		// GR_1024
	},
	{		//
			{491,347},		// GR_640
			{786,555}		// GR_1024
	},
	{		// gamma
			{453,395},		// GR_640
			{725,632}		// GR_1024
	}
};

static int Multi_ts_slot_text_coords[MULTI_TS_NUM_SHIP_SLOTS][GR_NUM_RESOLUTIONS][3] = {	// x,y,width
	{		// alpha
		{112,330,181-112},	// GR_640
		{187,517,181-112}		// GR_1024
	},
	{		// alpha
		{74,377,143-74},	// GR_640
		{126,592,143-74}	// GR_1024
	},
	{		// alpha 
		{149,377,218-149},// GR_640
		{248,592,218-149}	// GR_1024
	},
	{		// alpha
		{112,424,181-112},// GR_640
		{187,667,181-112}	// GR_1024
	},
	{		// beta
		{274,330,343-274},// GR_640
		{446,517,343-274}	// GR_1024
	},
	{		// beta
		{236,377,305-236},// GR_640
		{385,592,305-236}	// GR_1024
	},
	{		// beta
		{311,377,380-311},// GR_640
		{507,592,380-311}	// GR_1024
	},
	{		// beta
		{274,424,343-274},// GR_640
		{446,667,343-274}	// GR_1024
	},
	{		// gamma
		{437,330,506-437},// GR_640
		{707,517,506-437}	// GR_1024
	},
	{		// gamma
		{399,377,468-399},// GR_640
		{646,592,468-399}	// GR_1024
	},
	{		// gamma
		{474,377,543-474},// GR_640
		{768,592,543-474}	// GR_1024
	},
	{		// gamma
		{437,424,506-437},// GR_640
		{707,667,506-437}	// GR_1024
	}
};

// avail ship list data
#define MULTI_TS_AVAIL_MAX_DISPLAY		4
static int Multi_ts_avail_coords[MULTI_TS_AVAIL_MAX_DISPLAY][GR_NUM_RESOLUTIONS][2] = {							// x,y coords
	{		// 
			{23,331},	// GR_640
			{37,530}		// GR_1024
	},
	{		// 
			{23,361},	// GR_640
			{37,578}		// GR_1024
	},
	{		// 
			{23,391},	// GR_640
			{37,626}		// GR_1024
	},
	{		//
			{23,421},	// GR_640
			{37,674}		// GR_1024
	}
};
int Multi_ts_avail_start = 0;																		// starting index of where we will display the available ships
int Multi_ts_avail_count = 0;																		// the # of available ship classes

// ship information stuff
#define MULTI_TS_SHIP_INFO_MAX_LINE_LEN				150
#define MULTI_TS_SHIP_INFO_MAX_LINES					10
#define MULTI_TS_SHIP_INFO_MAX_TEXT						(MULTI_TS_SHIP_INFO_MAX_LINE_LEN * MULTI_TS_SHIP_INFO_MAX_LINES)

static int Multi_ts_ship_info_coords[GR_NUM_RESOLUTIONS][3] = {
	{ // GR_640
		33, 150, 387
	},
	{ // GR_1024
		53, 240, 618
	}
};

char Multi_ts_ship_info_lines[MULTI_TS_SHIP_INFO_MAX_LINES][MULTI_TS_SHIP_INFO_MAX_LINE_LEN];
char Multi_ts_ship_info_text[MULTI_TS_SHIP_INFO_MAX_TEXT];
int Multi_ts_ship_info_line_count;

// status bar mode
static int Multi_ts_status_coords[GR_NUM_RESOLUTIONS][3] = {
	{ // GR_640
		95, 467, 426
	},
	{ // GR_1024
		152, 747, 688
	}
};

int Multi_ts_status_bar_mode = 0;

// carried icon information
int Multi_ts_carried_flag = 0;
int Multi_ts_clicked_flag = 0;
int Multi_ts_clicked_x,Multi_ts_clicked_y;
int Multi_ts_carried_ship_class;
int Multi_ts_carried_from_type = 0;
int Multi_ts_carried_from_index = 0;

// selected ship types (for informational purposes)
int Multi_ts_select_type = -1;
int Multi_ts_select_index = -1;
int Multi_ts_select_ship_class = -1;

// per-frame mouse hotspot vars
int Multi_ts_hotspot_type = -1;
int Multi_ts_hotspot_index = -1;

// operation types			
#define TS_GRAB_FROM_LIST									0
#define TS_SWAP_LIST_SLOT									1
#define TS_SWAP_SLOT_SLOT									2
#define TS_DUMP_TO_LIST										3
#define TS_SWAP_PLAYER_PLAYER								4
#define TS_MOVE_PLAYER										5

// team data
#define MULTI_TS_FLAG_NONE									-2						// slot is _always_ empty
#define MULTI_TS_FLAG_EMPTY								-1						// flag is temporarily empty
typedef struct ts_team_data {
	int multi_ts_objnum[MULTI_TS_NUM_SHIP_SLOTS];							// objnums for all slots in this team
	net_player *multi_ts_player[MULTI_TS_NUM_SHIP_SLOTS];					// net players corresponding to the same slots
	int multi_ts_flag[MULTI_TS_NUM_SHIP_SLOTS];								// flags indicating the "status" of a slot
	int multi_players_locked;														// are the players locked into place
} ts_team_data;
ts_team_data Multi_ts_team[MULTI_TS_MAX_TVT_TEAMS];								// data for all teams

// deleted ship objnums
int Multi_ts_deleted_objnums[MULTI_TS_MAX_TVT_TEAMS * MULTI_TS_NUM_SHIP_SLOTS];
int Multi_ts_num_deleted;

//XSTR:ON

// ------------------------------------------------------------------------------------------------------
// TEAM SELECT FORWARD DECLARATIONS
//

// check for button presses
void multi_ts_check_buttons();

// act on a button press
void multi_ts_button_pressed(int n);

// initialize all screen data, etc
void multi_ts_init_graphics();

// blit all of the icons representing all wings
void multi_ts_blit_wings();

// blit all of the player callsigns under the correct ships
void multi_ts_blit_wing_callsigns();

// blit the ships on the avail list
void multi_ts_blit_avail_ships();

// initialize the snazzy menu stuff for dragging ships,players around
void multi_ts_init_snazzy();

// what type of region the index is (0 == ship avail list, 1 == ship slots, 2 == player slot)
int multi_ts_region_type(int region);

// convert the region num to a ship slot index
int multi_ts_slot_index(int region);

// convert the region num to an avail list index
int multi_ts_avail_index(int region);

// convert the region num to a player slot index
int multi_ts_player_index(int region);

// blit the status bar
void multi_ts_blit_status_bar();

// assign the correct players to the correct slots
void multi_ts_init_players();

// assign the correct objnums to the correct slots
void multi_ts_init_objnums();

// assign the correct flags to the correct slots
void multi_ts_init_flags();

// handle an available ship scroll down button press
void multi_ts_avail_scroll_down();

// handle an available ship scroll up button press
void multi_ts_avail_scroll_up();

// handle all mouse events (clicking, dragging, and dropping)
void multi_ts_handle_mouse();

// can the specified player perform the action he is attempting
int multi_ts_can_perform(int from_type,int from_index,int to_type,int to_index,int ship_class,int player_index = -1);

// determine the kind of drag and drop operation this is
int multi_ts_get_dnd_type(int from_type,int from_index,int to_type,int to_index,int player_index = -1);

// swap two player positions
int multi_ts_swap_player_player(int from_index,int to_index,interface_snd_id *sound,int player_index = -1);

// move a player
int multi_ts_move_player(int from_index,int to_index,interface_snd_id *sound,int player_index = -1);

// get the ship class of the current index in the avail list or -1 if none exists
int multi_ts_get_avail_ship_class(int index);

// blit the currently carried icon (if any)
void multi_ts_blit_carried_icon();

// if the (console) player is allowed to grab a player slot at this point
int multi_ts_can_grab_player(int slot_index,int player_index = -1);

// return the bitmap index into the ships icon array (in ship select) which should be displayed for the given slot
int multi_ts_slot_bmap_num(int slot_index);

// blit any active ship information text
void multi_ts_blit_ship_info();

// select the given slot and setup any information, etc
void multi_ts_select_ship();				

// is it ok for this player to commit 
int multi_ts_ok_to_commit();

// return the bitmap index into the ships icon array (in ship select) which should be displayed for the given slot
int multi_ts_avail_bmap_num(int slot_index);

// set the status bar to reflect the status of wing slots (free or not free). 0 or 1 are valid values for now
void multi_ts_set_status_bar_mode(int m);

// check to see that no illegal ship settings have occurred
void multi_ts_check_errors();

// ------------------------------------------------------------------------------------------------------
// TEAM SELECT FUNCTIONS
//

// initialize the team select screen (always call, even when switching between weapon select, etc)
void multi_ts_init()
{
	// if we haven't initialized at all yet, then do it
	if(!Multi_ts_inited){
		multi_ts_init_graphics();
		Multi_ts_inited = 1;
	}	

	// use the common interface palette
	multi_common_set_palette();

	Net_player->state = NETPLAYER_STATE_SHIP_SELECT;

	Current_screen = ON_SHIP_SELECT;
}

// initialize all critical internal data structures
void multi_ts_common_init()
{
	int idx;	
	
	// reset timestamps here. they seem to get hosed by the loadinh of the mission file
	multi_reset_timestamps();

	// saying "not allowed to mess with ships"
	Multi_ts_status_bar_mode = 0;		

	// intialize ship info stuff
	memset(Multi_ts_ship_info_text,0,MULTI_TS_SHIP_INFO_MAX_TEXT);
	memset(Multi_ts_ship_info_lines,0,MULTI_TS_SHIP_INFO_MAX_TEXT);
	Multi_ts_ship_info_line_count = 0;			

	// initialize carried icon information	
	Multi_ts_carried_flag = 0;
	Multi_ts_clicked_flag = 0;
	Multi_ts_clicked_x = 0;
	Multi_ts_clicked_y = 0;
	Multi_ts_carried_ship_class = -1;
	Multi_ts_carried_from_type = 0;
	Multi_ts_carried_from_index = 0;

	// selected slot information (should be default player ship)
	if(!MULTI_PERM_OBSERVER(Net_players[MY_NET_PLAYER_NUM])){
		Multi_ts_select_type = MULTI_TS_SLOT_LIST;
		Multi_ts_select_index = Net_player->p_info.ship_index;
		
		// select this ship and setup his info
		Multi_ts_select_ship_class = Wss_slots[Multi_ts_select_index].ship_class;
		multi_ts_select_ship();
	} else {
		Multi_ts_select_type = -1;
		Multi_ts_select_index = -1;

		// no ship class selected for information purposes
		Multi_ts_select_ship_class = -1;
	}

	// deleted ship information
	memset(Multi_ts_deleted_objnums,0,sizeof(int) * MULTI_TS_MAX_TVT_TEAMS * MULTI_TS_NUM_SHIP_SLOTS);
	Multi_ts_num_deleted = 0;

	// mouse hotspot information
	Multi_ts_hotspot_type = -1;
	Multi_ts_hotspot_index = -1;

	// initialize avail ship list data
	Multi_ts_avail_start = 0;

	// load the locked button bitmaps bitmaps
	for(idx=0;idx<MULTI_TS_NUM_LOCKED_BITMAPS;idx++){
		Multi_ts_locked_bitmaps[idx] = -1;	
		Multi_ts_locked_bitmaps[idx] = bm_load(Multi_ts_bmap_names[gr_screen.res][idx]);
	}	

	// assign the correct players to the correct slots
	multi_ts_init_players();

	// assign the correct objnums to the correct slots
	multi_ts_init_objnums();

	// sync the interface as normal
	multi_ts_sync_interface();	
}

void multi_ts_common_level_init()
{
	// blast the team data clean
	memset(Multi_ts_team,0,sizeof(ts_team_data) * MULTI_TS_MAX_TVT_TEAMS);
}

// do frame for team select
void multi_ts_do()
{	
	int k = chatbox_process();
	k = Multi_ts_window.process(k);

	// process any keypresses
	switch(k){
	case KEY_ESC :		
		gamesnd_play_iface(InterfaceSounds::USER_SELECT);
		multi_quit_game(PROMPT_ALL);
		break;	

	// cycle to the weapon select screen
	case KEY_TAB :
		gamesnd_play_iface(InterfaceSounds::USER_SELECT);
		Next_screen = ON_WEAPON_SELECT;
		gameseq_post_event(GS_EVENT_WEAPON_SELECTION);
		break;

	case KEY_ENTER|KEY_CTRLED:
	//	multi_ts_commit_pressed();
		Commit_pressed = 1;
		break;
	}		

	// check any button presses
	multi_ts_check_buttons();	

	// handle all mouse related events
	multi_ts_handle_mouse();

	// check for errors
	multi_ts_check_errors();
	
	// draw the background, etc
	gr_reset_clip();	
	GR_MAYBE_CLEAR_RES(Multi_ts_bitmap);
	if(Multi_ts_bitmap != -1){
		gr_set_bitmap(Multi_ts_bitmap);
		gr_bitmap(0,0,GR_RESIZE_MENU);
	}
	Multi_ts_window.draw();

	// render all wings
	multi_ts_blit_wings();

	// blit all callsigns
	multi_ts_blit_wing_callsigns();

	// blit the ships on the available list
	multi_ts_blit_avail_ships();

	// force draw the ship select button
	Multi_ts_buttons[gr_screen.res][MULTI_TS_SHIP_SELECT].button.draw_forced(2);	

	// force draw the "locked" button if necessary
	if(multi_ts_is_locked()){
		Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.draw_forced(2);
	} else {
		if( ((Netgame.type_flags & NG_TYPE_TEAM) && !(Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN)) ||
			 ((Netgame.type_flags & NG_TYPE_TEAM) && !(Net_player->flags & NETINFO_FLAG_GAME_HOST)) ){
			Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.draw_forced(0);
		} else {
			Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.draw();
		}
	}

	// blit any active ship information
	multi_ts_blit_ship_info();

	// blit the status bar
	multi_ts_blit_status_bar();	

	// render the chatbox
	chatbox_render();	

	// render tooltips
	Multi_ts_window.draw_tooltip();

	// display the status of the voice system
	multi_common_voice_display_status();

	// blit any carried icons
	multi_ts_blit_carried_icon();
	
	// flip the buffer
	gr_flip();

	// If the commit button was pressed, do the commit button actions.  Done at the end of the
	// loop since we are a bit special and have to close out in the proper order.
	if ( Commit_pressed ) {		
		multi_ts_commit_pressed();		
		Commit_pressed = 0;
	}
}

// close the team select screen (always call, even when switching between weapon select, etc)
void multi_ts_close()
{
	int idx;
	
	if(!Multi_ts_inited){
		return;
	}

	Multi_ts_inited = 0;
	
	// shut down the snazzy menu
	snazzy_menu_close();
	
	// unload any bitmaps
	if(!bm_unload(Multi_ts_bitmap)){
		nprintf(("General","WARNING : could not unload background bitmap %s\n",Multi_ts_bitmap_fname[gr_screen.res]));
	}		
	for(idx=0;idx<MULTI_TS_NUM_LOCKED_BITMAPS;idx++){
		if(Multi_ts_locked_bitmaps[idx] != -1){
			bm_release(Multi_ts_locked_bitmaps[idx]);
			Multi_ts_locked_bitmaps[idx] = -1;
		}
	}
	
	// destroy the UI_WINDOW
	Multi_ts_window.destroy();
}

// is the given slot disabled for the specified player
int multi_ts_disabled_slot(int slot_num, int player_index)
{
	net_player *pl;

	// get the appropriate net player
	if(player_index == -1){
		pl = Net_player;
	} else {
		pl = &Net_players[player_index];
	}

	// if the player is an observer, its _always_ disabled
	if(pl->flags & NETINFO_FLAG_OBSERVER){
		return 1;
	}

	// if the flag for this team isn't set to "free" we can't do anything
	if(!Multi_ts_team[pl->p_info.team].multi_players_locked){
		return 1;
	}

	// if the "leaders" only flag is set
	if(Netgame.options.flags & MSO_FLAG_SS_LEADERS){
		// in a team vs. team situation
		if(Netgame.type_flags & NG_TYPE_TEAM){
			if(pl->flags & NETINFO_FLAG_TEAM_CAPTAIN){
				return 0;
			}
		} 
		// in a non team vs. team situation
		else {
			if(pl->flags & NETINFO_FLAG_GAME_HOST){
				return 0;
			}
		}
	} else {	
		// in a team vs. team situation
		if(Netgame.type_flags & NG_TYPE_TEAM){
			// if i'm the team captain I can mess with my own ships as well as those of the ai ships on my team
			if(pl->flags & NETINFO_FLAG_TEAM_CAPTAIN){
				if((Multi_ts_team[pl->p_info.team].multi_ts_player[slot_num] != NULL) && (Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[slot_num]].flags[Object::Object_Flags::Player_ship]) && (slot_num != pl->p_info.ship_index)){
					return 1;
				}

				return 0;
			}
		}
		// in a non team vs. team situation
		else {			
			// if we're the host, we can our own ship and ai ships
			if(pl->flags & NETINFO_FLAG_GAME_HOST){
				// can't grab player ships
				if((Multi_ts_team[pl->p_info.team].multi_ts_player[slot_num] != NULL) && (Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[slot_num]].flags[Object::Object_Flags::Player_ship]) && (slot_num != pl->p_info.ship_index)){
					return 1;
				}

				return 0;
			}
		}

		// if this is our slot, then we can grab it
		if(slot_num == pl->p_info.ship_index){
			return 0;
		}
	}

	return 1;
}

// is the given slot disabled for the specified player, _and_ it is his ship as well
int multi_ts_disabled_high_slot(int slot_index,int player_index)
{
	net_player *pl;

	// get the appropriate net player
	if(player_index == -1){
		pl = Net_player;
	} else {
		pl = &Net_players[player_index];
	}

	// if this is disabled for him and its also _his_ slot
	if(multi_ts_disabled_slot(slot_index,player_index) && !Multi_ts_team[pl->p_info.team].multi_players_locked && (slot_index == pl->p_info.ship_index)){
		return 1;
	}

	return 0;
}

// resynch all display/interface elements based upon all the ship/weapon pool values
void multi_ts_sync_interface()
{
	int idx;
	
	// item 1 - determine how many ship types are available in the ship pool
	Multi_ts_avail_count = 0;
	for(idx = 0; idx < ship_info_size(); idx++) {
		if(Ss_pool[idx] > 0){
			Multi_ts_avail_count++;
		}
	}

	// item 2 - make sure our local Multi_ts_slot_flag array is up to date
	multi_ts_init_flags();
	
	// item 3 - set/unset any necessary flags in underlying ship select data structures
	for(idx=0;idx<MAX_WSS_SLOTS;idx++){
		switch(Multi_ts_team[Net_player->p_info.team].multi_ts_flag[idx]){
		case MULTI_TS_FLAG_EMPTY :		
			ss_make_slot_empty(idx);
			break;
		case MULTI_TS_FLAG_NONE :
			break;
		default :		
			ss_make_slot_full(idx);
			break;
		}
	}

	// item 4 - reset the locked/unlocked status of all ships in the weapon select screen
	ss_recalc_multiplayer_slots();
}

void multi_ts_assign_players_all()
{
	int idx,team_index,slot_index,found,player_count,shipnum;	
	char name_lookup[100];
	object *objp;	
	
	// set all player ship indices to -1
	for(idx=0;idx<MAX_PLAYERS;idx++){
		if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx])){
			Net_players[idx].p_info.ship_index = -1;
		}
	}

	// merge the created object list with the actual object list so we have all available ships
	obj_merge_created_list();		

	// get the # of players currently in the game
	player_count = multi_num_players();

	// always assign the host to the wing leader of one of the TVT wings
	// this is valid for coop games as well because the first starting wing
	// and the first tvt wing must have the same name
	memset(name_lookup,0,100);

	if(Netgame.type_flags & NG_TYPE_TEAM) {
		sprintf(name_lookup, "%s 1", TVT_wing_names[Netgame.host->p_info.team]);
	}
	else {
		// To account for cases where <Wingname> 1 is not a player ship
		for (int i = 0; i < MAX_SHIPS_PER_WING; i++) {
			wing_bash_ship_name(name_lookup, TVT_wing_names[0], i + 1);
			if (!stricmp(name_lookup, Player_start_shipname))
				break;
		}
	}
		
	shipnum = ship_name_lookup(name_lookup);
	
	// if we couldn't find the ship for the host
	if(shipnum == -1){
		// Netgame.flags |= NG_FLAG_QUITTING;
		multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_SHIP_ASSIGN);
		return;
	}

	multi_ts_get_team_and_slot(Ships[shipnum].ship_name,&team_index,&slot_index);
	multi_assign_player_ship(NET_PLAYER_INDEX(Netgame.host),&Objects[Ships[shipnum].objnum],Ships[shipnum].ship_info_index);
	Netgame.host->p_info.ship_index = slot_index;
	Assert(Netgame.host->p_info.ship_index >= 0);
	Netgame.host->p_info.ship_class = Ships[shipnum].ship_info_index;
	Netgame.host->m_player->objnum = Ships[shipnum].objnum;						

	// for each netplayer, try and find a ship
	objp = GET_FIRST(&obj_used_list);
	while(objp != END_OF_LIST(&obj_used_list)){
		// find a valid player ship - ignoring the ship which was assigned to the host
		if((objp->flags[Object::Object_Flags::Player_ship]) && stricmp(Ships[objp->instance].ship_name,name_lookup) != 0){
			// determine what team and slot this ship is				
			multi_ts_get_team_and_slot(Ships[objp->instance].ship_name,&team_index,&slot_index);
			Assert((team_index != -1) && (slot_index != -1));

			// in a team vs. team situation
			if(Netgame.type_flags & NG_TYPE_TEAM){
				// find a player on this team who needs a ship
				found = 0;
				for(idx=0;idx<MAX_PLAYERS;idx++){
					if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_OBSERVER(Net_players[idx]) && (Net_players[idx].p_info.ship_index == -1) && (Net_players[idx].p_info.team == team_index)){
						found = 1;
						break;
					}
				}			
			}
			// in a non team vs. team situation			
			else {
				// find any player on this who needs a ship
				found = 0;
				for(idx=0;idx<MAX_PLAYERS;idx++){
					if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_OBSERVER(Net_players[idx]) && (Net_players[idx].p_info.ship_index == -1)){					
						found = 1;
						break;
					}
				}			
			}

			// if we found a player
			if(found){
				multi_assign_player_ship(idx,objp,Ships[objp->instance].ship_info_index);
				Net_players[idx].p_info.ship_index = slot_index;
				Assert(Net_players[idx].p_info.ship_index >= 0);
				Net_players[idx].p_info.ship_class = Ships[objp->instance].ship_info_index;
				Net_players[idx].m_player->objnum = OBJ_INDEX(objp);					
				
				// decrement the player count
				player_count--;
			} else {
                objp->flags.remove(Object::Object_Flags::Player_ship);
				obj_set_flags( objp, objp->flags + Object::Object_Flags::Could_be_player);
			}

			// if we've assigned all players, we're done
			if(player_count <= 0){
				break;
			}
		}		
		
		// move to the next item
		objp = GET_NEXT(objp);		
	}	
	
	// go through and change any ships marked as player ships to be COULD_BE_PLAYER
	if ( objp != END_OF_LIST(&obj_used_list) ) {
		for ( objp = GET_NEXT(objp); objp != END_OF_LIST(&obj_used_list); objp = GET_NEXT(objp) ) {
			if ( objp->flags[Object::Object_Flags::Player_ship] ){
                objp->flags.remove(Object::Object_Flags::Player_ship);
				obj_set_flags( objp, objp->flags + Object::Object_Flags::Could_be_player);
			}
		}
	}	
	
	if(Game_mode & GM_STANDALONE_SERVER){
		Player_obj = NULL;
		//Net_player->m_player->objnum = -1;
	}

	// check to make sure all players were assigned correctly
	for(idx=0;idx<MAX_PLAYERS;idx++){
		if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_OBSERVER(Net_players[idx])){
			// if this guy never got assigned a player ship, there's a mission problem
			if(Net_players[idx].p_info.ship_index == -1){
				// Netgame.flags |= NG_FLAG_QUITTING;
				multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_SHIP_ASSIGN);
				return;
			}
		}
	}
}

// delete ships which have been removed from the game, tidy things 
void multi_ts_create_wings()
{
	int idx,s_idx;
	
	// the standalone never went through this screen so he should never call this function!
	// the standalone and all other clients will have this equivalent function performed whey they receive
	// the post_sync_data_packet!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
	Assert(!(Game_mode & GM_STANDALONE_SERVER));	
	
	// check status of all ships and delete or change ship type as necessary
	Multi_ts_num_deleted = 0;
	for(idx=0;idx<MULTI_TS_MAX_TVT_TEAMS;idx++){
		for(s_idx=0;s_idx<MULTI_TS_NUM_SHIP_SLOTS;s_idx++){	
			// otherwise if there's a valid ship in this spot
			if(Multi_ts_team[idx].multi_ts_flag[s_idx] >= 0){
				int objnum;

				// set the ship type appropriately
				Assert(Wss_slots_teams[idx][s_idx].ship_class >= 0);

				objnum = Multi_ts_team[idx].multi_ts_objnum[s_idx];
				change_ship_type(Objects[objnum].instance,Wss_slots_teams[idx][s_idx].ship_class);

				// set the ship weapons correctly
				wl_update_ship_weapons(objnum,&Wss_slots_teams[idx][s_idx]);
				
				// assign ts_index of the ship to point to the proper Wss_slots slot
				Ships[Objects[objnum].instance].ts_index = s_idx;
			} else if(Multi_ts_team[idx].multi_ts_flag[s_idx] == MULTI_TS_FLAG_EMPTY){		
				Assert(Multi_ts_team[idx].multi_ts_objnum[s_idx] >= 0);			

				// mark the object as having been deleted
				Multi_ts_deleted_objnums[Multi_ts_num_deleted] = Multi_ts_team[idx].multi_ts_objnum[s_idx];

				// delete the ship
				ship_add_exited_ship( &Ships[Objects[Multi_ts_deleted_objnums[Multi_ts_num_deleted]].instance], Ship::Exit_Flags::Player_deleted);
				obj_delete(Multi_ts_deleted_objnums[Multi_ts_num_deleted]);			
				ship_wing_cleanup(Objects[Multi_ts_deleted_objnums[Multi_ts_num_deleted]].instance,&Wings[Ships[Objects[Multi_ts_team[idx].multi_ts_objnum[s_idx]].instance].wingnum]);

				// increment the # of ships deleted
				Multi_ts_num_deleted++;
			}
		}
	}	
}

// do any necessary processing for players who have left the game
void multi_ts_handle_player_drop()
{
	int idx,s_idx;

	// find the player
	for(idx=0;idx<MULTI_TS_MAX_TVT_TEAMS;idx++){
		for(s_idx=0;s_idx<MULTI_TS_NUM_SHIP_SLOTS;s_idx++){
			// if we found him, clear his player slot and set his object back to being  OF_COULD_BE_PLAYER
			if((Multi_ts_team[idx].multi_ts_player[s_idx] != NULL) && !MULTI_CONNECTED((*Multi_ts_team[idx].multi_ts_player[s_idx]))){
				Assert(Multi_ts_team[idx].multi_ts_objnum[s_idx] != -1);
				Multi_ts_team[idx].multi_ts_player[s_idx] = NULL;
                Objects[Multi_ts_team[idx].multi_ts_objnum[s_idx]].flags.remove(Object::Object_Flags::Player_ship);
				obj_set_flags( &Objects[Multi_ts_team[idx].multi_ts_objnum[s_idx]], Objects[Multi_ts_team[idx].multi_ts_objnum[s_idx]].flags + Object::Object_Flags::Could_be_player);
			}
		}
	}
}

// set the status bar to reflect the status of wing slots (free or not free). 0 or 1 are valid values for now
void multi_ts_set_status_bar_mode(int m)
{
	Multi_ts_status_bar_mode = m;
}

// the "lock" button has been pressed
void multi_ts_lock_pressed()
{
	// do nothing if the button has already been pressed
	if(multi_ts_is_locked()){
		gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
		return;
	}
	
	if(Netgame.type_flags & NG_TYPE_TEAM){
		if (!(Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN)) {
			return;
		}
	} else {
		if (!(Net_player->flags & NETINFO_FLAG_GAME_HOST)) {
			return;
		}
	}
	gamesnd_play_iface(InterfaceSounds::USER_SELECT);

	// send a final player slot update packet		
	send_pslot_update_packet(Net_player->p_info.team,TS_CODE_LOCK_TEAM, interface_snd_id());
	Multi_ts_team[Net_player->p_info.team].multi_players_locked = 1;

	// sync interface stuff
	multi_ts_set_status_bar_mode(1);
	multi_ts_sync_interface();
	ss_recalc_multiplayer_slots();		

	// disable this button now
	Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.disable();
}

// if i'm "locked"
int multi_ts_is_locked()
{
	return Multi_ts_team[Net_player->p_info.team].multi_players_locked;
}

// show a popup saying "only host and team captains can modify, etc, etc"
void multi_ts_maybe_host_only_popup()
{
/*
	// if this is because the "host modifies" option is set				
				if((Netgame.options.flags & MSO_FLAG_SS_LEADERS) && !(Net_player->flags & NETINFO_FLAG_GAME_HOST) && !(Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
					multi_ts_host_only_popup();					
				}

	if(Netgame.type == NG_TYPE_TEAM){
		popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,"Only team captains may modify ships and weapons in this game");
	} else {
		popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,"Only the host may modify ships and weapons in this game");
	}
	*/
}


// ------------------------------------------------------------------------------------------------------
// TEAM SELECT FORWARD DEFINITIONS
//

// check for button presses
void multi_ts_check_buttons()
{
	int idx;
	for(idx=0;idx<MULTI_TS_NUM_BUTTONS;idx++){
		// we only really need to check for one button pressed at a time, so we can break after 
		// finding one.
		if(Multi_ts_buttons[gr_screen.res][idx].button.pressed()){
			multi_ts_button_pressed(idx);
			break;
		}
	}
}

// act on a button press
void multi_ts_button_pressed(int n)
{
	switch(n){
	// back to the briefing screen
	case MULTI_TS_BRIEFING :
		gamesnd_play_iface(InterfaceSounds::USER_SELECT);
		Next_screen = ON_BRIEFING_SELECT;
		gameseq_post_event( GS_EVENT_START_BRIEFING );
		break;
	// already on this screen
	case MULTI_TS_SHIP_SELECT:
		gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
		break;
	// back to the weapon select screen
	case MULTI_TS_WEAPON_SELECT:
		gamesnd_play_iface(InterfaceSounds::USER_SELECT);
		Next_screen = ON_WEAPON_SELECT;
		gameseq_post_event(GS_EVENT_WEAPON_SELECTION);
		break;
	// scroll the available ships list down
	case MULTI_TS_SHIPS_DOWN:		
		multi_ts_avail_scroll_down();
		break;
	// scroll the available ships list up
	case MULTI_TS_SHIPS_UP:		
		multi_ts_avail_scroll_up();
		break;
	// free ship/weapon select
	case MULTI_TS_LOCK:				
		Assert(Game_mode & GM_MULTIPLAYER);			
		// the "lock" button has been pressed
		multi_ts_lock_pressed();

		// disable the button if it is now locked
		if(multi_ts_is_locked()){
			Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.disable();
		}
		break;
	// commit button
	case MULTI_TS_COMMIT :				
	//	multi_ts_commit_pressed();
		Commit_pressed = 1;
		break;
	default :
		gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
		break;
	}
}

// initialize all screen data, etc
void multi_ts_init_graphics()
{
	int idx;
	
	// create the interface window
	Multi_ts_window.create(0,0,gr_screen.max_w_unscaled,gr_screen.max_h_unscaled,0);
	Multi_ts_window.set_mask_bmap(Multi_ts_bitmap_mask_fname[gr_screen.res]);

	// load the background bitmap
	Multi_ts_bitmap = mission_ui_background_load(Briefing->ship_select_background[gr_screen.res], Multi_ts_bitmap_fname[gr_screen.res]);

	if(Multi_ts_bitmap < 0){
		// we failed to load the bitmap - this is very bad
		Int3();
	}
			
	// create the interface buttons
	for(idx=0;idx<MULTI_TS_NUM_BUTTONS;idx++){
		// create the object
		if((idx == MULTI_TS_SHIPS_UP) || (idx == MULTI_TS_SHIPS_DOWN)){
			Multi_ts_buttons[gr_screen.res][idx].button.create(&Multi_ts_window, "", Multi_ts_buttons[gr_screen.res][idx].x, Multi_ts_buttons[gr_screen.res][idx].y, 1, 1, 1, 1);
		} else {
			Multi_ts_buttons[gr_screen.res][idx].button.create(&Multi_ts_window, "", Multi_ts_buttons[gr_screen.res][idx].x, Multi_ts_buttons[gr_screen.res][idx].y, 1, 1, 0, 1);
		}

		// set the sound to play when highlighted
		Multi_ts_buttons[gr_screen.res][idx].button.set_highlight_action(common_play_highlight_sound);

		// set the ani for the button
		Multi_ts_buttons[gr_screen.res][idx].button.set_bmaps(Multi_ts_buttons[gr_screen.res][idx].filename);

		// set the hotspot
		Multi_ts_buttons[gr_screen.res][idx].button.link_hotspot(Multi_ts_buttons[gr_screen.res][idx].hotspot);
	}		

	// add some text	
	Multi_ts_window.add_XSTR("Briefing", 765, Multi_ts_buttons[gr_screen.res][MULTI_TS_BRIEFING].xt, Multi_ts_buttons[gr_screen.res][MULTI_TS_BRIEFING].yt, &Multi_ts_buttons[gr_screen.res][MULTI_TS_BRIEFING].button, UI_XSTR_COLOR_GREEN);
	Multi_ts_window.add_XSTR("Ship Selection", 1067, Multi_ts_buttons[gr_screen.res][MULTI_TS_SHIP_SELECT].xt, Multi_ts_buttons[gr_screen.res][MULTI_TS_SHIP_SELECT].yt, &Multi_ts_buttons[gr_screen.res][MULTI_TS_SHIP_SELECT].button, UI_XSTR_COLOR_GREEN);
	Multi_ts_window.add_XSTR("Weapon Loadout", 1068, Multi_ts_buttons[gr_screen.res][MULTI_TS_WEAPON_SELECT].xt, Multi_ts_buttons[gr_screen.res][MULTI_TS_WEAPON_SELECT].yt, &Multi_ts_buttons[gr_screen.res][MULTI_TS_WEAPON_SELECT].button, UI_XSTR_COLOR_GREEN);
	Multi_ts_window.add_XSTR("Commit", 1062, Multi_ts_buttons[gr_screen.res][MULTI_TS_COMMIT].xt, Multi_ts_buttons[gr_screen.res][MULTI_TS_COMMIT].yt, &Multi_ts_buttons[gr_screen.res][MULTI_TS_COMMIT].button, UI_XSTR_COLOR_PINK);
	Multi_ts_window.add_XSTR("Lock", 1270, Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].xt, Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].yt, &Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button, UI_XSTR_COLOR_GREEN);

//	Multi_ts_window.add_XSTR("Help", 928, Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_HELP_BUTTON].xt, Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_HELP_BUTTON].yt, &Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_HELP_BUTTON].button, UI_XSTR_COLOR_GREEN);
//	Multi_ts_window.add_XSTR("Options", 1036, Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_OPTIONS_BUTTON].xt, Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_OPTIONS_BUTTON].yt, &Multi_ts_buttons[Current_screen-1][gr_screen.res][COMMON_OPTIONS_BUTTON].button, UI_XSTR_COLOR_GREEN);



	// make the ship scrolling lists

	// if we're not the host of the game (or a tema captain in team vs. team mode), disable the lock button
	if (Netgame.type_flags & NG_TYPE_TEAM) {
		if(!(Net_player->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
			Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.disable();
		}
	} else {
		if(!(Net_player->flags & NETINFO_FLAG_GAME_HOST)){
			Multi_ts_buttons[gr_screen.res][MULTI_TS_LOCK].button.disable();
		}
	}

	// initialize the snazzy menu stuff (for grabbing ships, names, etc)
	multi_ts_init_snazzy();

	// create the chatbox (again, should not be necessary at this point)	
	chatbox_create();	

	// sync the interface as normal
	multi_ts_sync_interface();
}

// blit all of the icons representing all wings
void multi_ts_blit_wings()
{
	int idx;

	// blit them all blindly for now
	for(idx=0;idx<MAX_WSS_SLOTS;idx++){
		// if this ship doesn't exist, then continue
		if(Multi_ts_team[Net_player->p_info.team].multi_ts_flag[idx] == MULTI_TS_FLAG_NONE){
			continue;
		}

		// otherwise blit the ship icon or the "empty" icon
		if(Multi_ts_team[Net_player->p_info.team].multi_ts_flag[idx] == MULTI_TS_FLAG_EMPTY){
			ss_blit_ship_icon(Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_X_COORD],Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_Y_COORD],-1,0);
		} else {
			ss_blit_ship_icon(Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_X_COORD],Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_Y_COORD],Wss_slots[idx].ship_class,multi_ts_slot_bmap_num(idx));

			// if this is a team vs team game, and the slot is occupised by a team captain, put a c there
			if((Netgame.type_flags & NG_TYPE_TEAM) && (Multi_ts_team[Net_player->p_info.team].multi_ts_player[idx] != NULL) && (Multi_ts_team[Net_player->p_info.team].multi_ts_player[idx]->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
				gr_set_color_fast(&Color_bright);
				gr_string(Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_X_COORD] - 5,Multi_ts_slot_icon_coords[idx][gr_screen.res][MULTI_TS_Y_COORD] - 5, XSTR("C",737), GR_RESIZE_MENU);  // [[ Team captain ]]
			}
		}
	}	
}

bool multi_ts_is_player_in_slot(int slot_idx)
{
	Assertion(slot_idx >= 0 && slot_idx < MAX_WSS_SLOTS, "Slot index is out of bounds!");
	Assertion(Net_player != nullptr, "Cannot check the slot for the player because the player is null!");

	return (Multi_ts_team[Net_player->p_info.team].multi_ts_player[slot_idx] != nullptr);
}

bool multi_ts_is_player_allowed_in_slot(int slot_idx)
{
	Assertion(slot_idx >= 0 && slot_idx < MAX_WSS_SLOTS, "Slot index is out of bounds!");
	Assertion(Net_player != nullptr, "Cannot check the slot for the player because the player is null!");

	p_object * pobj = mission_parse_get_arrival_ship(Ships[Objects[Multi_ts_team[Net_player->p_info.team].multi_ts_objnum[slot_idx]].instance].ship_name);			
	if ((pobj == NULL) || !(pobj->flags[Mission::Parse_Object_Flags::OF_Player_start])) {
		return false;
	}
	return true;
}

// blit all of the player callsigns under the correct ships
void multi_ts_blit_wing_callsigns()
{
	int idx,callsign_w;
	char callsign[CALLSIGN_LEN+2];

	// blit them all blindly for now
	for(idx=0;idx<MAX_WSS_SLOTS;idx++){		
		// if this ship doesn't exist, then continue
		if(Multi_ts_team[Net_player->p_info.team].multi_ts_flag[idx] == MULTI_TS_FLAG_NONE){
			continue;
		}		

		// if there is a player in the slot
		if (multi_ts_is_player_in_slot(idx)) {
			// make sure the string fits
			strcpy_s(callsign,Multi_ts_team[Net_player->p_info.team].multi_ts_player[idx]->m_player->callsign);
		} else {
			// determine if this is a locked AI ship		
            if (!multi_ts_is_player_allowed_in_slot(idx)) {
				strcpy_s(callsign, NOX("<"));
				strcat_s(callsign, XSTR("AI",738));  // [[ Artificial Intellegence ]]						
				strcat_s(callsign, NOX(">"));
			} else {
				strcpy_s(callsign, XSTR("AI",738));  // [[ Artificial Intellegence ]]						
			}
		}
			
		font::force_fit_string(callsign, CALLSIGN_LEN, Multi_ts_slot_text_coords[idx][gr_screen.res][MULTI_TS_W_COORD]);

		// get the final length
		gr_get_string_size(&callsign_w, NULL, callsign);

		// blit the string
		if((Multi_ts_hotspot_type == MULTI_TS_PLAYER_LIST) && (Multi_ts_hotspot_index == idx) && (Multi_ts_team[Net_player->p_info.team].multi_ts_player[idx] != NULL)){
			gr_set_color_fast(&Color_text_active_hi);
		} else {
			gr_set_color_fast(&Color_normal);
		}
		gr_string(Multi_ts_slot_text_coords[idx][gr_screen.res][MULTI_TS_X_COORD] + ((Multi_ts_slot_text_coords[idx][gr_screen.res][MULTI_TS_W_COORD] - callsign_w)/2),Multi_ts_slot_text_coords[idx][gr_screen.res][MULTI_TS_Y_COORD],callsign,GR_RESIZE_MENU);								
	}	
}

// blit the ships on the avail list
void multi_ts_blit_avail_ships()
{
	int display_count,ship_count,idx;
	char count[6];

	// blit the availability of all ship counts
	display_count = 0;
	ship_count = 0;
	for(idx = 0; idx < ship_info_size(); idx++) {
		if(Ss_pool[idx] > 0){
			// if our starting display index is after this, then skip it
			if(ship_count < Multi_ts_avail_start){
				ship_count++;
			} else {
				// blit the icon 
				ss_blit_ship_icon(Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_X_COORD],Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_Y_COORD],idx,multi_ts_avail_bmap_num(display_count));

				// blit the ship count available
				sprintf(count,"%d",Ss_pool[idx]);
				gr_set_color_fast(&Color_normal);
				gr_string(Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_X_COORD] - 20,Multi_ts_avail_coords[display_count][gr_screen.res][MULTI_TS_Y_COORD],count,GR_RESIZE_MENU);

				// increment the counts
				display_count++;				
				ship_count++;
			}
		}

		// if we've reached the max amount we can display, then stop
		if(display_count >= MULTI_TS_AVAIL_MAX_DISPLAY){
			return;
		}
	}
}

// initialize the snazzy menu stuff for dragging ships,players around
void multi_ts_init_snazzy()
{	
	// initialize the snazzy menu
	snazzy_menu_init();

	// blast the data
	Multi_ts_snazzy_regions = 0;
	Multi_ts_region.fill({});

	// get a pointer to the mask bitmap data
	Multi_ts_mask_data = Multi_ts_window.get_mask_data(&Multi_ts_mask_w, &Multi_ts_mask_h);

	// add the wing slots information
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_0_SHIP_0,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_0_SHIP_1,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_0_SHIP_2,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_0_SHIP_3,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_1_SHIP_0,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_1_SHIP_1,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_1_SHIP_2,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_1_SHIP_3,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_2_SHIP_0,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_2_SHIP_1,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_2_SHIP_2,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_2_SHIP_3,		0);

	// add the name slots information
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_0_NAME_0,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_0_NAME_1,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_0_NAME_2,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_0_NAME_3,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_1_NAME_0,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_1_NAME_1,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_1_NAME_2,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_1_NAME_3,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_2_NAME_0,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_2_NAME_1,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_2_NAME_2,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_2_NAME_3,		0);

	// add the available ships region
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_LIST_0,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_LIST_1,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_LIST_2,		0);
	snazzy_menu_add_region(&Multi_ts_region[Multi_ts_snazzy_regions++], "",	TSWING_LIST_3,		0);	
}

// what type of region the index is (0 == ship avail list, 1 == ship slots, 2 == player slot)
int multi_ts_region_type(int region)
{
	if((region == TSWING_0_SHIP_0) || (region == TSWING_0_SHIP_1) || (region == TSWING_0_SHIP_2) || (region == TSWING_0_SHIP_3) ||
		(region == TSWING_1_SHIP_0) || (region == TSWING_1_SHIP_1) || (region == TSWING_1_SHIP_2) || (region == TSWING_1_SHIP_3) ||
		(region == TSWING_2_SHIP_0) || (region == TSWING_2_SHIP_1) || (region == TSWING_2_SHIP_2) || (region == TSWING_2_SHIP_3) ){
		return MULTI_TS_SLOT_LIST;
	}

	if((region == TSWING_0_NAME_0) || (region == TSWING_0_NAME_1) || (region == TSWING_0_NAME_2) || (region == TSWING_0_NAME_3) ||
		(region == TSWING_1_NAME_0) || (region == TSWING_1_NAME_1) || (region == TSWING_1_NAME_2) || (region == TSWING_1_NAME_3) ||
		(region == TSWING_2_NAME_0) || (region == TSWING_2_NAME_1) || (region == TSWING_2_NAME_2) || (region == TSWING_2_NAME_3) ){
		return MULTI_TS_PLAYER_LIST;
	}

	if((region == TSWING_LIST_0) || (region == TSWING_LIST_1) || (region == TSWING_LIST_2) || (region == TSWING_LIST_3)){
		return MULTI_TS_AVAIL_LIST;
	}

	return -1;
}

// convert the region num to a ship slot index
int multi_ts_slot_index(int region)
{
	switch(region){
	case TSWING_0_SHIP_0:
		return 0;
	case TSWING_0_SHIP_1:
		return 1;
	case TSWING_0_SHIP_2:
		return 2;
	case TSWING_0_SHIP_3:
		return 3;
	case TSWING_1_SHIP_0:
		return 4;
	case TSWING_1_SHIP_1:
		return 5;
	case TSWING_1_SHIP_2:
		return 6;
	case TSWING_1_SHIP_3:
		return 7;
	case TSWING_2_SHIP_0:
		return 8;
	case TSWING_2_SHIP_1:
		return 9;
	case TSWING_2_SHIP_2:
		return 10;
	case TSWING_2_SHIP_3:
		return 11;
	}

	return -1;	
}

// convert the region num to an avail list index (starting from absolute 0)
int multi_ts_avail_index(int region)
{
	switch(region){
	case TSWING_LIST_0:
		return 0;
	case TSWING_LIST_1:
		return 1;
	case TSWING_LIST_2:
		return 2;
	case TSWING_LIST_3:
		return 3;
	}

	return -1;
}

// convert the region num to a player slot index
int multi_ts_player_index(int region)
{
	switch(region){
	case TSWING_0_NAME_0:
		return 0;
	case TSWING_0_NAME_1:
		return 1;
	case TSWING_0_NAME_2:
		return 2;
	case TSWING_0_NAME_3:
		return 3;
	case TSWING_1_NAME_0:
		return 4;
	case TSWING_1_NAME_1:
		return 5;
	case TSWING_1_NAME_2:
		return 6;
	case TSWING_1_NAME_3:
		return 7;
	case TSWING_2_NAME_0:
		return 8;
	case TSWING_2_NAME_1:
		return 9;
	case TSWING_2_NAME_2:
		return 10;
	case TSWING_2_NAME_3:
		return 11;
	}

	return -1;	
}

// blit any active ship information text
void multi_ts_blit_ship_info()
{
	int y_start, line_height;
	ship_info *sip;
	char str[100];

	// if we don't have a valid ship selected, do nothing
	if(Multi_ts_select_ship_class == -1){
		return;
	}

	// get the ship class
	sip = &Ship_info[Multi_ts_select_ship_class];

	// starting line
	y_start = Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_Y_COORD];

	line_height = gr_get_font_height() + 1;

	memset(str,0,100);

	// blit the ship class (name)
	gr_set_color_fast(&Color_normal);
	gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Class",739), GR_RESIZE_MENU);
	if(strlen(sip->get_display_name())){
		gr_set_color_fast(&Color_bright);
		gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->get_display_name(), GR_RESIZE_MENU);
	}
	y_start += line_height;

	// blit the ship type
	gr_set_color_fast(&Color_normal);
	gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Type",740), GR_RESIZE_MENU);
	if(sip->type_str){
		gr_set_color_fast(&Color_bright);
		gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->type_str.get(), GR_RESIZE_MENU);
	}
	y_start += line_height;

	// blit the ship length
	gr_set_color_fast(&Color_normal);
	gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Length",741), GR_RESIZE_MENU);
	if(sip->ship_length){
		gr_set_color_fast(&Color_bright);
		gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->ship_length.get(), GR_RESIZE_MENU);
	}
	y_start += line_height;

	// blit the max velocity
	gr_set_color_fast(&Color_normal);
	gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Max Velocity",742), GR_RESIZE_MENU);	
	sprintf(str, XSTR("%d m/s",743),(int)sip->max_vel.xyz.z);
	gr_set_color_fast(&Color_bright);
	gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start,str,GR_RESIZE_MENU);	
	y_start += line_height;

	// blit the maneuverability
	gr_set_color_fast(&Color_normal);
	gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Maneuverability",744), GR_RESIZE_MENU);
	if(sip->maneuverability_str){
		gr_set_color_fast(&Color_bright);
		gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->maneuverability_str.get(), GR_RESIZE_MENU);
	}
	y_start += line_height;

	// blit the armor
	gr_set_color_fast(&Color_normal);
	gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Armor",745), GR_RESIZE_MENU);
	if(sip->armor_str){
		gr_set_color_fast(&Color_bright);
		gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->armor_str.get(), GR_RESIZE_MENU);
	}
	y_start += line_height;

	// blit the gun mounts 
	gr_set_color_fast(&Color_normal);
	gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Gun Mounts",746), GR_RESIZE_MENU);
	if(sip->gun_mounts){
		gr_set_color_fast(&Color_bright);
		gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->gun_mounts.get(), GR_RESIZE_MENU);
	}
	y_start += line_height;

	// blit the missile banke
	gr_set_color_fast(&Color_normal);
	gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Missile Banks",747), GR_RESIZE_MENU);
	if(sip->missile_banks){
		gr_set_color_fast(&Color_bright);
		gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->missile_banks.get(), GR_RESIZE_MENU);
	}
	y_start += line_height;

	// blit the manufacturer
	gr_set_color_fast(&Color_normal);
	gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, XSTR("Manufacturer",748), GR_RESIZE_MENU);
	if(sip->manufacturer_str){
		gr_set_color_fast(&Color_bright);
		gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD] + 150, y_start, sip->manufacturer_str.get(), GR_RESIZE_MENU);
	}
	y_start += line_height;

	// blit the _short_ text description
	
	Assert(Multi_ts_ship_info_line_count < 3);
	gr_set_color_fast(&Color_normal);
	for(int idx=0;idx<Multi_ts_ship_info_line_count;idx++){
		gr_string(Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_X_COORD], y_start, Multi_ts_ship_info_lines[idx], GR_RESIZE_MENU);
		y_start += line_height;
	}
}


// blit the status bar
void multi_ts_blit_status_bar()
{
	char text[50];
	int text_w;
	int blit = 0;

	// mode specific text
	switch(Multi_ts_status_bar_mode){
	case 0 :
		strcpy_s(text, XSTR("Ships/Weapons Locked",749));
		blit = 1;
		break;
	case 1 :
		strcpy_s(text, XSTR("Ships/Weapons Are Now Free",750));
		blit = 1;
		break;
	}

	// if we should be blitting
	if(blit){	
		gr_get_string_size(&text_w,NULL,text);
		gr_set_color_fast(&Color_bright_blue);
		gr_string(Multi_ts_status_coords[gr_screen.res][MULTI_TS_X_COORD] + ((Multi_ts_status_coords[gr_screen.res][MULTI_TS_W_COORD] - text_w)/2),Multi_ts_status_coords[gr_screen.res][MULTI_TS_Y_COORD],text,GR_RESIZE_MENU);
	}
}

// assign the correct players to the correct slots
void multi_ts_init_players()
{
	int idx;

	// if i'm an observer, i have no ship
	if(Net_player->flags & NETINFO_FLAG_OBSERVER){
		Net_player->p_info.ship_index = -1;
	}

	// initialize all players and observer
	for(idx=0;idx<MAX_PLAYERS;idx++){
		if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx])){
			if(MULTI_OBSERVER(Net_players[idx])){
				Net_players[idx].p_info.ship_index = -1;
			} else {
				Multi_ts_team[Net_players[idx].p_info.team].multi_ts_player[Net_players[idx].p_info.ship_index] = &Net_players[idx];
			}
		}
	}
}

// assign the correct objnums to the correct slots
void multi_ts_init_objnums()
{
	int idx,s_idx,team_index,slot_index;

	// zero out the indices
	for(idx=0;idx<MULTI_TS_MAX_TVT_TEAMS;idx++){
		for(s_idx=0;s_idx<MULTI_TS_NUM_SHIP_SLOTS;s_idx++){
			Multi_ts_team[idx].multi_ts_objnum[s_idx] = -1;
		}		
	}

	// set all the objnums
	for (auto so: list_range(&Ship_obj_list)) {
		auto objp = &Objects[so->objnum];
		if (objp->flags[Object::Object_Flags::Should_be_dead])
			continue;

		// if its a ship, get its slot index (if any)
		multi_ts_get_team_and_slot(Ships[objp->instance].ship_name,&team_index,&slot_index);			
		if((slot_index != -1) && (team_index != -1)){
			Multi_ts_team[team_index].multi_ts_objnum[slot_index] = Ships[objp->instance].objnum;				
		}
	}		
}

// get the proper team and slot index for the given ship name
void multi_ts_get_team_and_slot(char* ship_name, int* team_index, int* slot_index)
{

	// set the return values to default values
	*team_index = -1;
	*slot_index = -1;

	// set up the ship registry pointer
	const ship_registry_entry* ship_regp = ship_registry_get(ship_name);

	// For a player usable ship, there has to be a parse object, a team and a position within a wing.
	Assert(ship_regp != nullptr && ship_regp->has_p_objp()); 
	if (ship_regp == nullptr || !ship_regp->has_p_objp()) {
		return;
	}

	// this should send the original team, in case the team changes via sexp or scripting
	*team_index = ship_regp->p_objp()->loadout_team;

	// if we're in team vs. team mode
	if(Netgame.type_flags & NG_TYPE_TEAM){
		// get the slot within the wing, since there's only one wing each in team vs. team.
		*slot_index = ship_regp->p_objp()->pos_in_wing;
	} 
	// if we're _not_ in team vs. team mode
	else {
		// get the wing index first.
		int wing_index = ship_regp->p_objp()->wing_status_wing_index;
		// only if the wing index is valid do we set a slot index.
		if (wing_index >= 0 && wing_index < MAX_STARTING_WINGS) {
			*slot_index = wing_index * MULTI_TS_NUM_SHIP_SLOTS_TEAM + ship_regp->p_objp()->pos_in_wing;
		}
	}
}

// function to return the shipname of the ship in the slot designated by the team and slot
// parameters
void multi_ts_get_shipname( char *ship_name, int team, int slot_index )
{
	if ( Netgame.type_flags & NG_TYPE_TEAM ) {
		Assert( (team >= 0) && (team < MULTI_TS_MAX_TVT_TEAMS) );
		wing_bash_ship_name(ship_name, TVT_wing_names[team], slot_index);
	} else {
		Assert( team == 0 );
		wing_bash_ship_name(ship_name, Starting_wing_names[slot_index / MULTI_TS_NUM_SHIP_SLOTS_TEAM], slot_index % MULTI_TS_NUM_SHIP_SLOTS_TEAM);
	}
}

// assign the correct flags to the correct slots
void multi_ts_init_flags()
{
	int idx,s_idx;

	// zero out the flags
	for(idx=0;idx<MULTI_TS_MAX_TVT_TEAMS;idx++){
		for(s_idx=0;s_idx<MULTI_TS_NUM_SHIP_SLOTS;s_idx++){
			Multi_ts_team[idx].multi_ts_flag[s_idx] = MULTI_TS_FLAG_NONE;			
		}
	}

	// in a team vs. team situation
	if(Netgame.type_flags & NG_TYPE_TEAM){
		for(idx=0;idx<MULTI_TS_MAX_TVT_TEAMS;idx++){
			for(s_idx=0;s_idx<MULTI_TS_NUM_SHIP_SLOTS_TEAM;s_idx++){
				// if the there is an objnum here but no ship class, we know its currently empty
				if((Multi_ts_team[idx].multi_ts_objnum[s_idx] != -1) && (Wss_slots_teams[idx][s_idx].ship_class == -1)){
					Multi_ts_team[idx].multi_ts_flag[s_idx] = MULTI_TS_FLAG_EMPTY;
				} else if((Multi_ts_team[idx].multi_ts_objnum[s_idx] != -1) && (Wss_slots_teams[idx][s_idx].ship_class != -1)){
					Multi_ts_team[idx].multi_ts_flag[s_idx] = Wss_slots_teams[idx][s_idx].ship_class;
				}
			}
		}
	}
	// in a non team vs. team situation
	else {
		for(idx=0;idx<MULTI_TS_NUM_SHIP_SLOTS;idx++){
			// if the there is an objnum here but no ship class, we know its currently empty
			if((Multi_ts_team[0].multi_ts_objnum[idx] != -1) && (Wss_slots[idx].ship_class == -1)){
				Multi_ts_team[0].multi_ts_flag[idx] = MULTI_TS_FLAG_EMPTY;
			} else if((Multi_ts_team[0].multi_ts_objnum[idx] != -1) && (Wss_slots[idx].ship_class != -1)){
				Multi_ts_team[0].multi_ts_flag[idx] = Wss_slots[idx].ship_class;
			}			
		}
	}
}

// handle an available ship scroll down button press
void multi_ts_avail_scroll_down()
{	
	if((Multi_ts_avail_count - Multi_ts_avail_start) > MULTI_TS_AVAIL_MAX_DISPLAY){
		gamesnd_play_iface(InterfaceSounds::USER_SELECT);
		Multi_ts_avail_start++;		
	} else {
		gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
	}
}

// handle an available ship scroll up button press
void multi_ts_avail_scroll_up()
{
	if(Multi_ts_avail_start > 0){
		gamesnd_play_iface(InterfaceSounds::USER_SELECT);
		Multi_ts_avail_start--;		
	} else {
		gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
	}
}

// handle all mouse events (clicking, dragging, and dropping)
void multi_ts_handle_mouse()
{
	int snazzy_region,snazzy_action;
	int region_type,region_index,region_empty;
	int mouse_x,mouse_y,ship_class;

	// get the mouse coords
	mouse_get_pos_unscaled(&mouse_x,&mouse_y);

	// do frame for the snazzy menu
	snazzy_region = snazzy_menu_do(Multi_ts_mask_data, Multi_ts_mask_w, Multi_ts_mask_h, Multi_ts_snazzy_regions, Multi_ts_region.data(), &snazzy_action, 0);

	region_type = -1;
	region_index = -1;
	region_empty = 1;
	ship_class = -1;
	if(snazzy_region != -1){
		region_type = multi_ts_region_type(snazzy_region);
		Assert(region_type != -1);

		// determine what type of region the mouse is over and the appropriate index
		switch(region_type){
		case MULTI_TS_AVAIL_LIST:
			region_index = multi_ts_avail_index(snazzy_region);
			ship_class = multi_ts_get_avail_ship_class(region_index);

			if(ship_class == -1){
				region_empty = 1;
			} else {
				region_empty = (Ss_pool[ship_class] > 0) ? 0 : 1;
			}
			break;
		case MULTI_TS_SLOT_LIST:
			region_index = multi_ts_slot_index(snazzy_region);
			if(region_index >= 0 ) {
				region_empty = (Multi_ts_team[Net_player->p_info.team].multi_ts_flag[region_index] >= 0) ? 0 : 1;
				if(!region_empty){
					ship_class = Wss_slots[region_index].ship_class;
				}
			}
			break;
		case MULTI_TS_PLAYER_LIST:
			region_index = multi_ts_player_index(snazzy_region);
			if(region_index >= 0 ) {
				region_empty = (Multi_ts_team[Net_player->p_info.team].multi_ts_player[region_index] != NULL) ? 0 : 1;
			}
			break;
		}
	}	

	// maybe play a "highlight" sound
	switch(region_type){
	case MULTI_TS_PLAYER_LIST:
		if((Multi_ts_hotspot_index != region_index) && (region_index >= 0) && (Multi_ts_team[Net_player->p_info.team].multi_ts_player[region_index] != NULL)){
			gamesnd_play_iface(InterfaceSounds::USER_SELECT);
		}
		break;
	}

	// set the current frame mouse hotspot vars
	Multi_ts_hotspot_type = region_type;
	Multi_ts_hotspot_index = region_index;

	// if we currently have clicked on something and have just released it
	if(!Multi_ts_carried_flag && Multi_ts_clicked_flag && !mouse_down(MOUSE_LEFT_BUTTON)){
		Multi_ts_clicked_flag = 0;
	}

	// if we're currently not carrying anything and the user has clicked
	if(!Multi_ts_carried_flag && !Multi_ts_clicked_flag && mouse_down(MOUSE_LEFT_BUTTON) && !region_empty){
		// set the "clicked" flag
		Multi_ts_clicked_flag = 1;

		// check to see if he clicked on a ship type and highlight if necessary
		switch(region_type){
		// selected a ship in the wing slots
		case MULTI_TS_SLOT_LIST:
			Multi_ts_select_type = MULTI_TS_SLOT_LIST;
			Multi_ts_select_index = region_index;
			multi_ts_select_ship();
			break;

		// selected a ship on the avail list
		case MULTI_TS_AVAIL_LIST:
			Multi_ts_select_type = MULTI_TS_AVAIL_LIST;
			Multi_ts_select_index = region_index;
			multi_ts_select_ship();
			break;
		
		// selected something else - unselect
		default :
			Multi_ts_select_type = -1;
			Multi_ts_select_index = -1;
			Multi_ts_select_ship_class = -1;
			break;
		}

		Multi_ts_clicked_x = mouse_x;
		Multi_ts_clicked_y = mouse_y;
	}

	// if we had something clicked and have started dragging it
	if(!Multi_ts_carried_flag && Multi_ts_clicked_flag && mouse_down(MOUSE_LEFT_BUTTON) && ((Multi_ts_clicked_x != mouse_x) || (Multi_ts_clicked_y != mouse_y))){
		// if this player is an observer, he shouldn't be able to do jack
		if(Net_player->flags & NETINFO_FLAG_OBSERVER){
			return;
		}

		// first we check for illegal conditions (any case where he cannot grab what he is attempting to grab)
		switch(region_type){
		case MULTI_TS_AVAIL_LIST :
			// if players are not yet locked, can't grab ships
			if(!Multi_ts_team[Net_player->p_info.team].multi_players_locked){
				return;
			}
			
			if(region_empty){
				return;
			}
			break;
		case MULTI_TS_SLOT_LIST:
			// if players are not yet locked, can't grab ships
			if(!Multi_ts_team[Net_player->p_info.team].multi_players_locked){
				return;
			}

			if(multi_ts_disabled_slot(region_index)){
				multi_ts_maybe_host_only_popup();			
				return;
			}
			if(region_index >= 0 ) {
				if(Multi_ts_team[Net_player->p_info.team].multi_ts_flag[region_index] < 0){
					return;
				}
			}
			break;
		case MULTI_TS_PLAYER_LIST:
			if(!multi_ts_can_grab_player(region_index)){
				return;
			}
			break;
		}
		
		
		Multi_ts_clicked_flag = 0;
		Multi_ts_carried_flag = 1;

		// set up the carried icon here
		Multi_ts_carried_from_type = region_type;
		Multi_ts_carried_from_index = region_index;
		Multi_ts_carried_ship_class = ship_class;
	}		

	// if we were carrying something but have dropped it
	if(Multi_ts_carried_flag && !mouse_down(MOUSE_LEFT_BUTTON)){
		Multi_ts_carried_flag = 0;
		Multi_ts_clicked_flag = 0;		

		// if we're not allowed to drop onto this slot
		if((region_type == MULTI_TS_SLOT_LIST) && multi_ts_disabled_slot(region_index)){
			multi_ts_maybe_host_only_popup();			
		}

		// if we're over some kind of valid region, apply		
		multi_ts_drop(Multi_ts_carried_from_type,Multi_ts_carried_from_index,region_type,region_index,Multi_ts_carried_ship_class);		
	}
}

// can the specified player perform the action he is attempting
int multi_ts_can_perform(int from_type,int from_index,int to_type,int to_index,int ship_class,int player_index)
{
	net_player *pl;
	int op_type;
	p_object *pobj;

	// get the appropriate player
	if(player_index == -1){
		pl = Net_player;
	} else {
		pl = &Net_players[player_index];
	}

	// get the operation type
	op_type = multi_ts_get_dnd_type(from_type,from_index,to_type,to_index,player_index);

	// if either of the indices are bogus, then bail
	if((from_index == -1) || (to_index == -1)){
		return 0;
	}

	switch(op_type){
	case TS_GRAB_FROM_LIST:
		// if there are no more of this ship class, its no go
		if(Ss_pool_teams[pl->p_info.team][ship_class] <= 0){
			return 0;
		}

		// if he's not allowed to touch the wing slot
		if(multi_ts_disabled_slot(to_index,player_index)){
			return 0;
		}

		// if the slot he's trying to drop it on is "permanently" empty
		if(Multi_ts_team[pl->p_info.team].multi_ts_flag[to_index] == MULTI_TS_FLAG_NONE){
			return 0;
		}
		break;

	case TS_SWAP_LIST_SLOT:
		// if there are no more of this ship class, its no go
		if(Ss_pool_teams[pl->p_info.team][ship_class] <= 0){
			return 0;
		}

		// if he's not allowed to touch the wing slot
		if(multi_ts_disabled_slot(to_index,player_index)){
			return 0;
		}

		// if the slot we're trying to move to is invalid, then do nothing
		if(Multi_ts_team[pl->p_info.team].multi_ts_flag[to_index] == MULTI_TS_FLAG_NONE){
			return 0;
		}
		break;

	case TS_SWAP_SLOT_SLOT:
		// if he's not allowed to touch one of the slots, its no go
		if(multi_ts_disabled_slot(from_index,player_index) || multi_ts_disabled_slot(to_index,player_index)){
			return 0;
		}

		// if the slot we're taking from is invalid
		if(Multi_ts_team[pl->p_info.team].multi_ts_flag[to_index] == MULTI_TS_FLAG_NONE){
			return 0;
		}
		break;

	case TS_DUMP_TO_LIST:
		// if he's not allowed to be touching the slot to begin with, it no go
		if(multi_ts_disabled_slot(from_index,player_index)){
			return 0;
		}

		// if the slot we're trying to move to is invalid, then do nothing
		if(Multi_ts_team[pl->p_info.team].multi_ts_flag[to_index] == MULTI_TS_FLAG_NONE){
			return 0;
		}
		break;

	case TS_SWAP_PLAYER_PLAYER:
		// if his team is already locked, he cannot do this
		if(Multi_ts_team[pl->p_info.team].multi_players_locked){
			return 0;
		}

		// if there isn't a player at one of the positions
		if((Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] == NULL) || (Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] == NULL)){
			return 0;
		}

		// if this is not a player ship type object
		if(Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index] != -1){
			pobj = mission_parse_get_arrival_ship(Ships[Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].instance].ship_name);
			if((pobj == NULL) || !(pobj->flags[Mission::Parse_Object_Flags::OF_Player_start])){
				return 0;
			}
		}		

		if(Netgame.type_flags & NG_TYPE_TEAM){
			// if he's not the team captain, he cannot do this
			if(!(pl->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
				return 0;
			}
		} else {
			// if he's not the host, he cannot do this
			if(!(pl->flags & NETINFO_FLAG_GAME_HOST)){
				return 0;
			}
		}
		break;		

	case TS_MOVE_PLAYER:
		// if his team is already locked, he cannot do this
		if(Multi_ts_team[pl->p_info.team].multi_players_locked){
			return 0;
		}

		// if there isn't a player at the _from_
		if(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] == NULL){
			return 0;
		}

		// if there is no ship at the _to_ location
		if(Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index] < 0){
			return 0;
		}

		// if this is not a player ship type object
		if(Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index] != -1){
			pobj = mission_parse_get_arrival_ship(Ships[Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].instance].ship_name);
			if((pobj == NULL) || !(pobj->flags[Mission::Parse_Object_Flags::OF_Player_start])){
				return 0;
			}
		}

		if(Netgame.type_flags & NG_TYPE_TEAM){
			// if he's not the team captain, he cannot do this
			if(!(pl->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
				return 0;
			}
		} else {
			// if he's not the host, he cannot do this
			if(!(pl->flags & NETINFO_FLAG_GAME_HOST)){
				return 0;
			}
		}
		break;

	default : 
		return 0;
		break;
	}
	
	return 1;
}

// determine the kind of drag and drop operation this is
int multi_ts_get_dnd_type(int from_type,int  /*from_index*/,int to_type,int to_index,int player_index)
{	
	net_player *pl;

	// get the appropriate player
	if(player_index == -1){
		pl = Net_player;
	} else {
		pl = &Net_players[player_index];
	}
	
	switch(from_type){
	// came from the ship avail list
	case MULTI_TS_AVAIL_LIST :	
		// do nothing
		if(to_type == MULTI_TS_AVAIL_LIST){
			return -1;
		}
		
		// if placing it on a slot
		if(to_type == MULTI_TS_SLOT_LIST){
			if(Wss_slots_teams[pl->p_info.team][to_index].ship_class == -1){				
				return TS_GRAB_FROM_LIST;				
			} else {								
				return TS_SWAP_LIST_SLOT;
			}
		}
		break;
	
	// came from the ship slots 
	case MULTI_TS_SLOT_LIST :
		if(to_type == MULTI_TS_SLOT_LIST){
			return TS_SWAP_SLOT_SLOT;
		}
		if(to_type == MULTI_TS_AVAIL_LIST){
			return TS_DUMP_TO_LIST;
		}
		break;	

	// came from the player lists
	case MULTI_TS_PLAYER_LIST :
		if(to_type == MULTI_TS_PLAYER_LIST){
			if(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] == NULL){
				return TS_MOVE_PLAYER;
			} else {
				return TS_SWAP_PLAYER_PLAYER;
			}
		}
		break;
	}

	return -1;
}

void multi_ts_apply(int from_type,int from_index,int to_type,int to_index,int ship_class,int player_index)
{
	int size,update;
	ubyte wss_data[MAX_PACKET_SIZE];
	net_player *pl;
	
	// determine what kind of operation this is
	int type = multi_ts_get_dnd_type(from_type,from_index,to_type,to_index,player_index);	

	// get the proper net player
	if(player_index == -1){
		pl = Net_player;
	} else {
		pl = &Net_players[player_index];
	}

	// set the proper pool pointers
	common_set_team_pointers(pl->p_info.team);

	interface_snd_id sound;
	switch(type){
	case TS_SWAP_SLOT_SLOT :
		nprintf(("Network","Apply swap slot slot %d %d\n",from_index,to_index));
		update = ss_swap_slot_slot(from_index,to_index,&sound);
		break;
	case TS_DUMP_TO_LIST	:
		nprintf(("Network","Apply dump to list %d %d\n",from_index,to_index));
		update = ss_dump_to_list(from_index,ship_class,&sound);
		break;
	case TS_SWAP_LIST_SLOT :
		nprintf(("Network","Apply swap list slot %d %d\n",from_index,to_index));
		update = ss_swap_list_slot(ship_class,to_index,&sound);
		break;
	case TS_GRAB_FROM_LIST :
		nprintf(("Network","Apply grab from list %d %d\n",from_index,to_index));		
		update = ss_grab_from_list(ship_class,to_index,&sound);
		break;
	case TS_SWAP_PLAYER_PLAYER :
		nprintf(("Network","Apply swap player player %d %d\n",from_index,to_index));
		update = multi_ts_swap_player_player(from_index,to_index,&sound,player_index);
		break;
	case TS_MOVE_PLAYER :
		nprintf(("Network","Apply move player %d %d\n",from_index,to_index));
		update = multi_ts_move_player(from_index,to_index,&sound,player_index);
		break;
	default :
		update = 0;
		break;
	}
	
	if(update){
		// if we're the host, send an update to all players
		if ( MULTIPLAYER_HOST ) {						
			// send the correct type of update
			if(type == TS_SWAP_PLAYER_PLAYER){
			} else {				
				size = store_wss_data(wss_data, MAX_PACKET_SIZE-20, sound, player_index);
				send_wss_update_packet(pl->p_info.team,wss_data, size);			

				// send a player slot update packet as well, so ship class information, etc is kept correct
				send_pslot_update_packet(pl->p_info.team,TS_CODE_PLAYER_UPDATE, interface_snd_id());
			}

			// if the player index == -1, it means the action was done locally - so play a sound
			if((player_index == -1) && (sound.isValid())){
				gamesnd_play_iface(sound);
			}
		}

		// sync the interface screen up ourselves, if necessary
		if(Net_player->p_info.team == pl->p_info.team){
			multi_ts_sync_interface();	
		}

		// make sure all flags are set properly for all teams
		multi_ts_init_flags();
	}	

	// set the proper pool pointers
	common_set_team_pointers(Net_player->p_info.team);
}

// drop a carried icon 
void multi_ts_drop(int from_type,int from_index,int to_type,int to_index,int ship_class,int player_index)
{
	// if I'm the host, apply immediately
	if(Net_player->flags & NETINFO_FLAG_GAME_HOST){
		// if this is a legal operation
		if(multi_ts_can_perform(from_type,from_index,to_type,to_index,ship_class,player_index)){
			multi_ts_apply(from_type,from_index,to_type,to_index,ship_class,player_index);
		} else {
			nprintf(("Network","Could not apply operation!\n"));
		}
	} 
	// otherwise send a request to the host
	else {
		send_wss_request_packet(Net_player->player_id, from_type, from_index, to_type, to_index, -1, ship_class, WSS_SHIP_SELECT);
	}
}

// swap two player positions
int multi_ts_swap_player_player(int from_index,int to_index,interface_snd_id *sound,int player_index)
{
	net_player *pl,*temp;

	// get the proper player pointer
	if(player_index == -1){
		pl = Net_player;
	} else {
		pl = &Net_players[player_index];
	}

	// swap the players
	temp = Multi_ts_team[pl->p_info.team].multi_ts_player[to_index];
	Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] = Multi_ts_team[pl->p_info.team].multi_ts_player[from_index];
	Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] = temp;

	// update netplayer information if necessary
	if(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] != NULL){
		Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]->p_info.ship_index = from_index;
		Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]->p_info.ship_class = Wss_slots_teams[pl->p_info.team][from_index].ship_class;

		multi_assign_player_ship(NET_PLAYER_INDEX(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]),&Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]],Wss_slots_teams[pl->p_info.team][from_index].ship_class);
	}
	if(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] != NULL){
		Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]->p_info.ship_index = to_index;
		Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]->p_info.ship_class = Wss_slots_teams[pl->p_info.team][to_index].ship_class;

		multi_assign_player_ship(NET_PLAYER_INDEX(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]),&Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]],Wss_slots_teams[pl->p_info.team][to_index].ship_class);
	}

	// update ship flags
    Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.remove(Object::Object_Flags::Could_be_player);
    Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.remove(Object::Object_Flags::Player_ship);
	if(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] != NULL){		
        Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.set(Object::Object_Flags::Player_ship);
	}		
	Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.remove(Object::Object_Flags::Could_be_player);
    Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.remove(Object::Object_Flags::Player_ship);
	if(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] != NULL){		
		Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.set(Object::Object_Flags::Player_ship);
	}		

	// recalcalate which slots are locked/unlocked, etc
	ss_recalc_multiplayer_slots();

	// send an update packet to all players
	if(Net_player->flags & NETINFO_FLAG_GAME_HOST){
		send_pslot_update_packet(pl->p_info.team,TS_CODE_PLAYER_UPDATE,InterfaceSounds::ICON_DROP_ON_WING);
		gamesnd_play_iface(InterfaceSounds::ICON_DROP_ON_WING);
	}

	*sound = InterfaceSounds::ICON_DROP;

	return 1;
}

// move a player
int multi_ts_move_player(int from_index,int to_index,interface_snd_id *sound,int player_index)
{
	net_player *pl;

	// get the proper player pointer
	if(player_index == -1){
		pl = Net_player;
	} else {
		pl = &Net_players[player_index];
	}

	// swap the players	
	Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] = Multi_ts_team[pl->p_info.team].multi_ts_player[from_index];
	Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] = NULL;

	// update netplayer information if necessary
	if(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] != NULL){
		Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]->p_info.ship_index = from_index;
		Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]->p_info.ship_class = Wss_slots_teams[pl->p_info.team][from_index].ship_class;

		multi_assign_player_ship(NET_PLAYER_INDEX(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index]),&Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]],Wss_slots_teams[pl->p_info.team][from_index].ship_class);
	}
	if(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] != NULL){
		Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]->p_info.ship_index = to_index;
		Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]->p_info.ship_class = Wss_slots_teams[pl->p_info.team][to_index].ship_class;

		multi_assign_player_ship(NET_PLAYER_INDEX(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index]),&Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]],Wss_slots_teams[pl->p_info.team][to_index].ship_class);
	}

	// update ship flags
	Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.remove(Object::Object_Flags::Could_be_player);
    Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.remove(Object::Object_Flags::Player_ship);
	if(Multi_ts_team[pl->p_info.team].multi_ts_player[to_index] != NULL){		
        Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[to_index]].flags.set(Object::Object_Flags::Player_ship);
	}		
	Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.remove(Object::Object_Flags::Could_be_player);
    Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.remove(Object::Object_Flags::Player_ship);
	if(Multi_ts_team[pl->p_info.team].multi_ts_player[from_index] != NULL){		
		Objects[Multi_ts_team[pl->p_info.team].multi_ts_objnum[from_index]].flags.set(Object::Object_Flags::Player_ship);
	}		

	// recalcalate which slots are locked/unlocked, etc
	ss_recalc_multiplayer_slots();

	// send an update packet to all players
	if(Net_player->flags & NETINFO_FLAG_GAME_HOST){
		send_pslot_update_packet(pl->p_info.team,TS_CODE_PLAYER_UPDATE,InterfaceSounds::ICON_DROP_ON_WING);
		gamesnd_play_iface(InterfaceSounds::ICON_DROP_ON_WING);
	}

	*sound = InterfaceSounds::ICON_DROP;

	return 1;
}

// get the ship class of the current index in the avail list or -1 if none exists
int multi_ts_get_avail_ship_class(int index)
{
	int ship_count,class_index;

	ship_count = index + Multi_ts_avail_start;
	class_index = 0;
	while((ship_count >= 0) && (class_index < ship_info_size())){
		if(Ss_pool[class_index] > 0){
			ship_count--;
		}

		if(ship_count >= 0){
			class_index++;
		}
	}

	if(ship_count < 0){
		return class_index;
	}

	return -1;
}

// blit the currently carried icon (if any)
void multi_ts_blit_carried_icon()
{
	int x,y;
	int offset_x,offset_y,callsign_w;
	char callsign[CALLSIGN_LEN+2];

	// if we're not carrying anything, then return
	if(!Multi_ts_carried_flag){
		return;
	}	

	// get the mouse position
	mouse_get_pos_unscaled(&x,&y);

	// if we're carrying an icon of some kind
	switch(Multi_ts_carried_from_type){
	case MULTI_TS_SLOT_LIST:	
		offset_x = Multi_ts_slot_icon_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_X_COORD] - Multi_ts_clicked_x;
		offset_y = Multi_ts_slot_icon_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_Y_COORD] - Multi_ts_clicked_y;

		// blit the icon		
		ss_blit_ship_icon(x + offset_x,y + offset_y,Multi_ts_carried_ship_class,0);
		break;
	case MULTI_TS_AVAIL_LIST:
		offset_x = Multi_ts_avail_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_X_COORD] - Multi_ts_clicked_x;
		offset_y = Multi_ts_avail_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_Y_COORD] - Multi_ts_clicked_y;

		// blit the icon		
		ss_blit_ship_icon(x + offset_x,y + offset_y,Multi_ts_carried_ship_class,0);
		break;
	case MULTI_TS_PLAYER_LIST:
		// get the final length of the string so we can calculate a valid offset
		strcpy_s(callsign,Multi_ts_team[Net_player->p_info.team].multi_ts_player[Multi_ts_carried_from_index]->m_player->callsign);
		font::force_fit_string(callsign,CALLSIGN_LEN,Multi_ts_slot_text_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_W_COORD]);						
		gr_get_string_size(&callsign_w,NULL,callsign);

		// calculate the offsets
		offset_x = (Multi_ts_slot_text_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_X_COORD] - Multi_ts_clicked_x) + ((Multi_ts_slot_text_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_W_COORD] - callsign_w)/2);
		offset_y = Multi_ts_slot_text_coords[Multi_ts_carried_from_index][gr_screen.res][MULTI_TS_Y_COORD] - Multi_ts_clicked_y;

		gr_set_color_fast(&Color_normal);
		gr_string(x + offset_x,y + offset_y,Multi_ts_team[Net_player->p_info.team].multi_ts_player[Multi_ts_carried_from_index]->m_player->callsign,GR_RESIZE_MENU);
		break;
	default : 
		break;			
	}
}

// if the (console) player is allowed to grab a player slot at this point
int multi_ts_can_grab_player(int slot_index, int player_index)
{
	net_player *pl;

	// get a pointe rto the proper net player
	if(player_index == -1){
		pl = Net_player;
	} else {
		pl = &Net_players[player_index];
	}

	// if the players are locked in any case, he annot grab it
	if(Multi_ts_team[pl->p_info.team].multi_players_locked){
		return 0;
	}

	if(Netgame.type_flags & NG_TYPE_TEAM){
		// if he's not the team captain, he cannot do this
		if(!(pl->flags & NETINFO_FLAG_TEAM_CAPTAIN)){
			return 0;
		}
	} else {
		// if he's not the host, he cannot do this
		if(!(pl->flags & NETINFO_FLAG_GAME_HOST)){
			return 0;
		}
	}

	// if the slot is empty
	if(Multi_ts_team[pl->p_info.team].multi_ts_player[slot_index] == NULL){
		return 0;
	}

	return 1;
}

// get the team # of the given ship
int multi_ts_get_team(char *ship_name)
{
	int idx;//,s_idx;

	// lookup through all team ship names
	Assert(MAX_TVT_WINGS == MULTI_TS_MAX_TVT_TEAMS);
	for(idx=0;idx<MAX_TVT_WINGS;idx++)
	{
		if (!strnicmp(ship_name, TVT_wing_names[idx], strlen(TVT_wing_names[idx])))
			return idx;
	}

	// always on team 0 if not found otherwise
	return 0;
}

// return the bitmap index into the ships icon array (in ship select) which should be displayed for the given slot
int multi_ts_avail_bmap_num(int slot_index)
{
	// if this slot has been highlighted for informational purposes
	if((Multi_ts_select_type == MULTI_TS_AVAIL_LIST) && (Multi_ts_select_index == slot_index)){
		return ICON_FRAME_SELECTED;
	}

	// if its otherwise being lit by the mouse
	if((Multi_ts_hotspot_type == MULTI_TS_AVAIL_LIST) && (Multi_ts_hotspot_index == slot_index)){
		return ICON_FRAME_HOT;
	}	

	return ICON_FRAME_NORMAL;
}

// return the bitmap index into the ships icon array (in ship select) which should be displayed for the given slot
int multi_ts_slot_bmap_num(int slot_index)
{	
	// special case - slot is disabled, its my ship and the host hasn't locked the ships yet
	if(multi_ts_disabled_high_slot(slot_index)){
		return ICON_FRAME_DISABLED_HIGH;
	}	
	
	// if this slot is disabled for us, then show it as such	
	if(multi_ts_disabled_slot(slot_index)){
		return ICON_FRAME_DISABLED;
	}

	// if this slot has been highlighted for informational purposes
	if((Multi_ts_select_type == MULTI_TS_SLOT_LIST) && (Multi_ts_select_index == slot_index)){
		return ICON_FRAME_SELECTED;
	}

	// if this is our ship, then highlight it as so
	if(Net_player->p_info.ship_index == slot_index){
		return ICON_FRAME_PLAYER;
	}

	// if its otherwise being lit by the mouse
	if((Multi_ts_hotspot_type == MULTI_TS_SLOT_LIST) && (Multi_ts_hotspot_index == slot_index)){
		return ICON_FRAME_HOT;
	}	

	// normal unhighlighted frame
	return ICON_FRAME_NORMAL;
}

// select the given slot and setup any information, etc
void multi_ts_select_ship()
{
	int n_lines;
	int n_chars[MAX_BRIEF_LINES];
	const char *p_str[MAX_BRIEF_LINES];
	char *token;
	
	
	// blast all current text
	memset(Multi_ts_ship_info_lines,0,MULTI_TS_SHIP_INFO_MAX_TEXT);
	memset(Multi_ts_ship_info_text,0,MULTI_TS_SHIP_INFO_MAX_TEXT);

	// get the selected ship class
	Assert(Multi_ts_select_index >= 0);
	Multi_ts_select_ship_class = -1;
	switch(Multi_ts_select_type){
	case MULTI_TS_SLOT_LIST:
		Multi_ts_select_ship_class = Wss_slots[Multi_ts_select_index].ship_class;
		break;
	case MULTI_TS_AVAIL_LIST:
		Multi_ts_select_ship_class = multi_ts_get_avail_ship_class(Multi_ts_select_index);
		// if he has selected an empty slot, don't do anything
		if(Multi_ts_select_ship_class < 0){
			return;
		}
		break;
	default : 
		// should always have one of the 2 above types selected
		Int3();
		break;
	}
	
	// split the text info up	
	
	Assert(Multi_ts_select_ship_class >= 0);
	if (Ship_info[Multi_ts_select_ship_class].desc)
	{
		// strip out newlines
		auto ship_desc = util::unique_copy(Ship_info[Multi_ts_select_ship_class].desc.get(), true);
		token = strtok(ship_desc.get(), "\n");
		if(token != NULL){
			strcpy_s(Multi_ts_ship_info_text,token);
			while(token != NULL){
				token = strtok(NULL,"\n");
				if(token != NULL){
					strcat_s(Multi_ts_ship_info_text," ");
					strcat_s(Multi_ts_ship_info_text,token);
				}
			}
		}
	
		if(Multi_ts_ship_info_text[0] != '\0'){
			// split the string into multiple lines
			n_lines = split_str(Multi_ts_ship_info_text, Multi_ts_ship_info_coords[gr_screen.res][MULTI_TS_W_COORD], n_chars, p_str, MULTI_TS_SHIP_INFO_MAX_LINES, MULTI_TS_SHIP_INFO_MAX_LINE_LEN,0);

			// copy the split up lines into the text lines array
			for (int idx = 0;idx<n_lines;idx++ ) {
				Assert(n_chars[idx] < MULTI_TS_SHIP_INFO_MAX_LINE_LEN);
				strncpy(Multi_ts_ship_info_lines[idx], p_str[idx], n_chars[idx]);
				Multi_ts_ship_info_lines[idx][n_chars[idx]] = 0;
				drop_leading_white_space(Multi_ts_ship_info_lines[idx]);		
			}

			// get the line count
			Multi_ts_ship_info_line_count = n_lines;
		} else {
			// set the line count to 
			Multi_ts_ship_info_line_count = 0;
		}
	}	
}

// handle all details when the commit button is pressed (including possibly reporting errors/popups)
commit_pressed_status multi_ts_commit_pressed()
{					
	// if my team's slots are still not "locked", we cannot commit unless we're the only player in the game
	if(!Multi_ts_team[Net_player->p_info.team].multi_players_locked){
		extern int red_alert_mission();
		// skip this for red-alert missions too since nothing is selectable
		if ( !red_alert_mission() && (multi_num_players() != 1) ) {
			popup(PF_USE_AFFIRMATIVE_ICON | PF_BODY_BIG,1,POPUP_OK, XSTR("Players have not yet been assigned to their ships",751));
			return commit_pressed_status::MULTI_PLAYERS_NO_SHIPS;
		} else {
			Multi_ts_team[Net_player->p_info.team].multi_players_locked = 1;
		}
	}
	commit_pressed_status rc = commit_pressed_status::GENERAL_FAIL;

	// check to see if its not ok for this player to commit
	switch(multi_ts_ok_to_commit()){
	// yes, it _is_ ok to commit
	case 0:
		rc = commit_pressed();
		break;

	// player has not assigned all necessary ships
	case 1: 	
		gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
		rc = commit_pressed_status::MULTI_NOT_ALL_ASSIGNED;
		popup(PF_USE_AFFIRMATIVE_ICON | PF_BODY_BIG,1,POPUP_OK, XSTR("You have not yet assigned all necessary ships",752));
		break;
	
	// there are ships without primary weapons
	case 2: 
		gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
		rc = commit_pressed_status::MULTI_NO_PRIMARY;
		popup(PF_USE_AFFIRMATIVE_ICON | PF_BODY_BIG,1,POPUP_OK, XSTR("There are ships without primary weapons!",753));
		break;

	// there are ships without secondary weapons
	case 3: 
		gamesnd_play_iface(InterfaceSounds::GENERAL_FAIL);
		rc = commit_pressed_status::MULTI_NO_SECONDARY;
		popup(PF_USE_AFFIRMATIVE_ICON | PF_BODY_BIG,1,POPUP_OK, XSTR("There are ships without secondary weapons!",754));
		break;
	}

	return rc;
}

// is it ok for this player to commit 
int multi_ts_ok_to_commit()
{
	int idx,s_idx;
	int primary_ok,secondary_ok;	

	// if this player is an observer, he can always commit
	if(Net_player->flags & NETINFO_FLAG_OBSERVER){
		return 0;
	}
	
	for(idx=0;idx<MULTI_TS_NUM_SHIP_SLOTS;idx++){
		// if this is a player slot this player can modify and it is empty, then he cannot continue
		// implies there is never an object in this slot
		if((Multi_ts_team[Net_player->p_info.team].multi_ts_objnum[idx] != -1) &&							
			// implies player can't touch this slot anyway			
			!multi_ts_disabled_slot(idx) &&								
			// implies that there should be a player ship here but there isn't
			((Multi_ts_team[Net_player->p_info.team].multi_ts_player[idx] != NULL) && (Multi_ts_team[Net_player->p_info.team].multi_ts_flag[idx] == MULTI_TS_FLAG_EMPTY)) ){
			return 1;
		}

		// if the ship in this slot has a ship which can be a player but has 0 primary or secondary weapons, then he cannot continue
		if( (Multi_ts_team[Net_player->p_info.team].multi_ts_objnum[idx] != -1) && 
			((Objects[Multi_ts_team[Net_player->p_info.team].multi_ts_objnum[idx]].flags[Object::Object_Flags::Could_be_player]) ||
			 (Objects[Multi_ts_team[Net_player->p_info.team].multi_ts_objnum[idx]].flags[Object::Object_Flags::Player_ship])) &&
			 !multi_ts_disabled_slot(idx)){
			
			primary_ok = 0;
			secondary_ok = 0;
			// go through all weapons in the list
			for(s_idx=0;s_idx<MAX_SHIP_WEAPONS;s_idx++){
				// if this slot has a weapon with a greater than 0 count, check
				if((Wss_slots_teams[Net_player->p_info.team][idx].wep[s_idx] >= 0) && (Wss_slots_teams[Net_player->p_info.team][idx].wep_count[s_idx] > 0)){
					switch(Weapon_info[Wss_slots_teams[Net_player->p_info.team][idx].wep[s_idx]].subtype){				
					case WP_LASER:
						primary_ok = 1;
						break;

					case WP_MISSILE:
						secondary_ok = 1;
						break;

					default :
						Int3();
					}
				}

				// if we've got both primary and secondary weapons
				if(primary_ok && secondary_ok){
					break;
				}
			}

			// if the ship doesn't have primary weapons
			if (!primary_ok && !(The_mission.ai_profile->flags[AI::Profile_Flags::Multi_allow_empty_secondaries])) {
				return 2;
			} 

			// if the ship doesn't have secondary weapons
			if (!secondary_ok && !(The_mission.ai_profile->flags[AI::Profile_Flags::Multi_allow_empty_secondaries])) {
				return 3;
			}
		}
	}
			
	return 0;
}

// check to see that no illegal ship settings have occurred
void multi_ts_check_errors()
{
	/*
	int idx;
	ship *shipp;

	for(idx=0;idx<MULTI_TS_NUM_SHIP_SLOTS;idx++){
		if(Multi_ts_team[0].multi_ts_objnum[idx] == -1){
			continue;
		}

		shipp = &Ships[Objects[Multi_ts_team[0].multi_ts_objnum[idx]].instance];
		Assert((shipp->weapons.current_primary_bank != -1) && (shipp->weapons.current_secondary_bank != -1));
	}
	*/
}


// ------------------------------------------------------------------------------------------------------
// TEAM SELECT PACKET HANDLERS
//

// send a player slot position update
void send_pslot_update_packet(int team,int code, interface_snd_id sound)
{
	ubyte data[MAX_PACKET_SIZE],stop,val;
	short s_sound;
	int idx;
	int packet_size = 0;
	int i_tmp;

	// build the header and add the data
	BUILD_HEADER(SLOT_UPDATE);	

	// add the opcode
	val = (ubyte)code;
	ADD_DATA(val);

	// add the team 
	val = (ubyte)team;
	ADD_DATA(val);

	// add the sound to play
	s_sound = (short)sound.value();
	ADD_SHORT(s_sound);
	
	// add data based upon the packet code
	switch(code){
	case TS_CODE_LOCK_TEAM:
		// don't have to do anything
		break;
	case TS_CODE_PLAYER_UPDATE:
		// only the host should ever be doing this
		Assert(Net_player->flags & NETINFO_FLAG_GAME_HOST);
			
		// add individual slot data
		for(idx=0;idx<MAX_WSS_SLOTS;idx++){
			if(Multi_ts_team[team].multi_ts_flag[idx] != MULTI_TS_FLAG_NONE){
				// add a stop byte
				stop = 0x0;
				ADD_DATA(stop);

				// add the slot #
				val = (ubyte)idx;
				ADD_DATA(val);

				// add the ship class
				ADD_SHORT(static_cast<short>(Wss_slots_teams[team][idx].ship_class));

				// add the objnum we're working with
				i_tmp = Multi_ts_team[team].multi_ts_objnum[idx];
				ADD_INT(i_tmp);

				// add a byte indicating if a player is here or not
				if(Multi_ts_team[team].multi_ts_player[idx] == NULL){
					val = 0;
				} else {
					val = 1;
				} 
				ADD_DATA(val);

				// if there's a player, add his address
				if(val){
					ADD_SHORT(Multi_ts_team[team].multi_ts_player[idx]->player_id);

					// should also update his p_info settings locally
					Multi_ts_team[team].multi_ts_player[idx]->p_info.ship_class = Wss_slots_teams[team][idx].ship_class;
					Multi_ts_team[team].multi_ts_player[idx]->p_info.ship_index = idx;
				}
				
				// add a byte indicating what object flag should be set (0 == ~(OF_COULD_BE_PLAYER | OF_PLAYER_SHIP), 1 == player ship, 2 == could be player ship)				
				if(Objects[Multi_ts_team[team].multi_ts_objnum[idx]].flags[Object::Object_Flags::Could_be_player]){
					val = 2;
				} else if(Objects[Multi_ts_team[team].multi_ts_objnum[idx]].flags[Object::Object_Flags::Player_ship]){
					val = 1;
				} else {
					val = 0;
				}
				ADD_DATA(val);
			}
		}
		// add a final stop byte
		val = 0xff;
		ADD_DATA(val);
		break;
	default :
		Int3();
		break;
	}
	
	// send the packet to the standalone	
	if(Net_player->flags & NETINFO_FLAG_AM_MASTER) {
		multi_io_send_to_all_reliable(data, packet_size);		
	} else {
		multi_io_send_reliable(Net_player, data, packet_size);
	}
}

// process a player slot position update
void process_pslot_update_packet(ubyte *data, header *hinfo)
{
	int offset = HEADER_LENGTH;
	int my_index;
	int player_index,idx,team,code,objnum;
	short sound_id;
	short player_id;
	ubyte stop, val, slot_num;
	short ship_class;

	my_index = Net_player->p_info.ship_index;

	// if we're the standalone, then we should be routing this data to all the other clients
	player_index = -1;
	if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
		// fill in the address information of where this came from		
		player_index = find_player_index(hinfo->id);
		Assert(player_index != -1);		
	}

	// get the opcode
	GET_DATA(val);
	code = (int)val;

	// get the team
	GET_DATA(val);
	team = (int)val;

	// get the sound to play
	GET_SHORT(sound_id);
	auto sound = interface_snd_id(sound_id);

	// process the different opcodes
	switch(code){
	case TS_CODE_LOCK_TEAM:
		// lock the team
		Multi_ts_team[team].multi_players_locked = 1;

		// if this was my team, sync stuff up
		if((team == Net_player->p_info.team) && !(Game_mode & GM_STANDALONE_SERVER)){
			multi_ts_set_status_bar_mode(1);
			multi_ts_sync_interface();
			ss_recalc_multiplayer_slots();
		}

		// if this is the standalone server, we need to re-route the packet here and there
		if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
			// in team vs team mode, only a team captain should ever be sending this
			if(Netgame.type_flags & NG_TYPE_TEAM){
				Assert(Net_players[player_index].flags & NETINFO_FLAG_TEAM_CAPTAIN);
			}
			// in any other mode, it better be coming from the game host
			else {
				Assert(Net_players[player_index].flags & NETINFO_FLAG_GAME_HOST);
			}

			// re-route to all other players
			for(idx=0;idx<MAX_PLAYERS;idx++){
				if(MULTI_CONNECTED(Net_players[idx]) && (&Net_players[idx] != Net_player) && (&Net_players[idx] != &Net_players[player_index]) ){					
					multi_io_send_reliable(&Net_players[idx], data, offset);
				}
			}
		}
		break;
	case TS_CODE_PLAYER_UPDATE:			
		// get the first stop byte
		GET_DATA(stop);
		while(stop != 0xff){
			// get the slot #
			GET_DATA(slot_num);

			// get the ship class
			GET_SHORT(ship_class);

			// get the objnum
			GET_INT(objnum);
	
			// flag indicating if a player is in this slot
			GET_DATA(val);
			if(val){
				// look the player up
				GET_SHORT(player_id);
				player_index = find_player_index(player_id);
			
				// if we couldn't find him
				if(player_index == -1){
					nprintf(("Network","Couldn't find player for pslot update!\n"));
					Multi_ts_team[team].multi_ts_player[slot_num] = NULL;
				} 
				// if we found him, assign him to this ship
				else {
					Net_players[player_index].p_info.ship_class = ship_class;
					Net_players[player_index].p_info.ship_index = (int)slot_num;
					multi_assign_player_ship(player_index, &Objects[objnum], ship_class);

					// ui stuff
					Multi_ts_team[team].multi_ts_player[slot_num] = &Net_players[player_index];

					// if this was me and my ship index changed, update the weapon select screen
					if(my_index != Net_player->p_info.ship_index){
						wl_reset_selected_slot();

						my_index = Net_player->p_info.ship_index;
					}
				}
			} else {
				Multi_ts_team[team].multi_ts_player[slot_num] = NULL;
			}

			// get the ship flag byte
			GET_DATA(val);
            Objects[objnum].flags.remove(Object::Object_Flags::Player_ship); 
            Objects[objnum].flags.remove(Object::Object_Flags::Could_be_player);
			switch(val){
			case 1 :
                Objects[objnum].flags.set(Object::Object_Flags::Player_ship);
				break;
			case 2 :
				obj_set_flags( &Objects[objnum], Objects[objnum].flags + Object::Object_Flags::Could_be_player);
				break;
			}

			// get the next stop byte
			GET_DATA(stop);
		}
		// if we have a sound we're supposed to play
		if((sound.isValid()) && !(Game_mode & GM_STANDALONE_SERVER) && (gameseq_get_state() == GS_STATE_TEAM_SELECT)){
			gamesnd_play_iface(sound);
		}

		// if i'm the standalone server, I should rebroadcast this packet
		if(Game_mode & GM_STANDALONE_SERVER){
			for(idx=0;idx<MAX_PLAYERS;idx++){
				if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_HOST(Net_players[idx]) && (Net_player != &Net_players[idx])){					
					multi_io_send_reliable(&Net_players[idx], data, offset);
				}
			}
		}
		break;
	}	
	PACKET_SET_SIZE();				

	// recalculate stuff
	if(!(Game_mode & GM_STANDALONE_SERVER)){
		ss_recalc_multiplayer_slots();
	}
}
