/*
 * 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/multi.h"
#include "network/multiutil.h"
#include "network/multimsgs.h"
#include "ship/ship.h"
#include "io/timer.h"
#include "playerman/player.h"
#include "mission/missionparse.h"
#include "mission/missioncampaign.h"
#include "gamesequence/gamesequence.h"
#include "freespace.h"
#include "osapi/osapi.h"
#include "network/stand_gui.h"
#include "network/multi_xfer.h"
#include "network/multiui.h"
#include "network/multi_ingame.h"
#include "popup/popup.h"
#include "missionui/chatbox.h"
#include "model/modelreplace.h"
#include "network/multiteamselect.h"
#include "network/multi_data.h"
#include "network/multi_kick.h"
#include "network/multi_campaign.h"
#include "network/multi_voice.h"
#include "network/multi_team.h"
#include "network/multi_respawn.h"
#include "network/multi_pmsg.h"
#include "network/multi_endgame.h"
#include "missionui/missiondebrief.h"
#include "network/multi_pause.h"
#include "mission/missiongoals.h"
#include "network/multi_log.h"
#include "network/multi_rate.h"
#include "network/multi_lua.h"
#include "hud/hudescort.h"
#include "hud/hudmessage.h"
#include "globalincs/alphacolors.h"
#include "globalincs/pstypes.h"
#include "cfile/cfile.h"
#include "network/multi_fstracker.h"
#include "network/multi_sw.h"
#include "network/multi_portfwd.h"
#include "network/multi_turret_manager.h"
#include "pilotfile/pilotfile.h"
#include "debugconsole/console.h"
#include "network/psnet2.h"
#include "network/multi_mdns.h"
#include "cmdline/cmdline.h"

// Stupid windows workaround...
#ifdef MessageBox
#undef MessageBox
#endif



// ----------------------------------------------------------------------------------------
// Basic module scope defines
//
//

// timestamp defines
#define NETGAME_SEND_TIME								2						// time between sending netgame update packets
#define STATE_SEND_TIME									2						// time between sending netplayer state packets
#define GAMEINFO_SEND_TIME								3						// time between sending game information packets
#define PING_SEND_TIME									2						// time between player pings
#define BYTES_SENT_TIME									5						// every five seconds

// local network buffer stuff
#define MAX_NET_BUFFER									(1024 * 16)			// define and variable declaration for our local tcp buffer
#define NUM_REENTRANT_LEVELS							3

// time (in fixed seconds) to put up dialog about no contect from server
#define MULTI_SERVER_MAX_TIMEOUT						(F1_0 * 4)					// after this number of milliseoncds, stop client simulation
#define MULTI_SERVER_MAX_TIMEOUT_LARGE				(F1_0 * 40)					// done anytime not in mission
#define MULTI_SERVER_WAIT_TIME						(F1_0 * 60)					// wait 60 seconds to reconnect with the server
#define MULTI_SERVER_GONE								1
#define MULTI_SERVER_ALIVE								2

// define for when to show "slow network" icon
#define MULTI_SERVER_SLOW_PING_TIME					700					// when average ping time to server reaches this -- display hud icon

// update times for clients ships based on object update level
int Multi_client_update_intervals[MAX_OBJ_UPDATE_LEVELS]	= 
{
	333,				// Dialup, 3x a second
	166,				// Medium, 6x a second
	80,					// High, 12.5x a second
	30,					// LAN, 33x a second
};					

int Multi_display_netinfo = 1;

// ----------------------------------------------------------------------------------------
// Multiplayer vars
//
//

// net player vars		
net_player Net_players[MAX_PLAYERS];							// array of all netplayers in the game
net_player *Net_player;												// pointer to console's net_player entry

// netgame vars
netgame_info Netgame;												// netgame information
int Multi_mission_loaded = 0;										// flag, so that we don't load the mission more than once client side
int Ingame_join_net_signature = -1;								// signature for the player obj for use when joining ingame
int Multi_button_info_ok = 0;										// flag saying it is ok to apply critical button info on a client machine
int Multi_button_info_id = 0;										// identifier of the stored button info to be applying

// misc data
SCP_list<active_game> Active_games;							// list of active games displayed on the Join screen
CFILE* Multi_chat_stream;											// for streaming multiplayer chat strings to a file
int Multi_connection_speed;										// connection speed of this machine.
int Multi_num_players_at_start = 0;								// the # of players present (kept track of only on the server) at the very start of the mission
short Multi_id_num = 0;												// for assigning player id #'s

// permanent server list
server_item* Game_server_head;								// list of permanent game servers to be querying

// timestamp data
int Netgame_send_time = -1;							// timestamp used to send netgame info to players before misison starts
time_t State_send_time = -1;								// timestamp used to send state information to the host before a mission starts
int Gameinfo_send_time = -1;							// timestamp used by master to send game information to clients
time_t Next_ping_time = -1;								// when we should next ping all
int Multi_server_check_count = 0;					// var to keep track of reentrancy when checking server status
int Next_bytes_time = -1;								// bytes sent

// how often each player gets updated
UI_TIMESTAMP Multi_client_update_times[MAX_PLAYERS];	// client update packet timestamp

// local network buffer data
LOCAL ubyte net_buffer[NUM_REENTRANT_LEVELS][MAX_NET_BUFFER];
LOCAL ubyte Multi_read_count;

UI_TIMESTAMP Multi_restr_query_timestamp;
join_request Multi_restr_join_request;
net_addr Multi_restr_addr;
int Multi_join_restr_mode = -1;

LOCAL fix Multi_server_wait_start;				// variable to hold start time when waiting to reestablish with server

// non API master tracker vars
char Multi_tracker_login[MULTI_TRACKER_STRING_LEN+1] = "";
char Multi_tracker_passwd[MULTI_TRACKER_STRING_LEN+1] = "";
char Multi_tracker_squad_name[MULTI_TRACKER_STRING_LEN+1] = "";
int Multi_tracker_id = -1;
char Multi_tracker_id_string[255];

// current file checksum
ushort Multi_current_file_checksum = 0;
int Multi_current_file_length = -1;


// -------------------------------------------------------------------------------------------------
//	multi_init() is called only once, at game start-up.  Get player address + port, initialize the
// network players list.
//
//

void multi_init()
{
	int idx;

	// read in config file
	multi_options_read_config();

	Assert( Net_player == NULL );
	Multi_id_num = 0;

	// clear out all netplayers
	for(idx=0; idx<MAX_PLAYERS; idx++){
		Net_players[idx].init();
		Net_players[idx].reliable_socket = PSNET_INVALID_SOCKET;
	}

	// initialize the local netplayer
	Net_player = &Net_players[0];	
	Net_player->tracker_player_id = Multi_tracker_id;
	Net_player->m_player = Player;
	Net_player->flags = 0;	
	Net_player->s_info.xfer_handle = -1;
	Net_player->player_id = multi_get_new_id();
	Net_player->client_cinfo_seq = 0;
	Net_player->client_server_seq = 0;		

	// get our connection speed
	Multi_connection_speed = multi_get_connection_speed();			
	
	// initialize other stuff
	multi_log_init();

	// load up common multiplayer icons
	if (!Is_standalone)
		multi_load_common_icons();	

	// delete mvalid.cfg if it exists
	cf_delete(MULTI_VALID_MISSION_FILE, CF_TYPE_DATA);
}

// this is an important function which re-initializes any variables required in multiplayer games. 
// Always make sure globals you add are re-initialized here !!!!
void multi_vars_init()
{
	// initialize this variable right away.  Used in game_level_init for init'ing the player
	Next_ship_signature = SHIP_SIG_MIN;		
	Next_asteroid_signature = ASTEROID_SIG_MIN;
	Next_non_perm_signature = NPERM_SIG_MIN;   
	Next_debris_signature = DEBRIS_SIG_MIN;
	Next_waypoint_signature = WAYPOINT_SIG_MIN;
	
	// server-client critical stuff
	Multi_button_info_ok = 0;
	Multi_button_info_id = 0;

	// Ingame join stuff
	Ingame_join_net_signature = -1;

	// Netgame stuff
	Netgame.game_state = NETGAME_STATE_FORMING;	

	// team select stuff
	Multi_ts_inited = 0;	

	// load send stuff
	Multi_mission_loaded = 0;   // client side		

	// restricted game stuff
	Multi_restr_query_timestamp = UI_TIMESTAMP::invalid();

	// respawn stuff	
	Multi_server_check_count = 0;

	// reentrant variable
	Multi_read_count = 0;

	// current file checksum
	Multi_current_file_checksum = 0;
	Multi_current_file_length = -1;

	Active_games.clear();
	Game_server_head = NULL;

	// only the server should ever care about this
	Multi_id_num = 0;
}

// -------------------------------------------------------------------------------------------------
//	multi_level_init() is called whenever the player starts a multiplayer game
//
//

void multi_level_init() 
{
	int idx;

	// NETLOG
	ml_string(NOX("multi_level_init()"));

	// initialize the Net_players array
	for ( idx = 0; idx < MAX_PLAYERS; idx++) {
		// close all sockets down just for good measure
		psnet_rel_close_socket(Net_players[idx].reliable_socket);

		Net_players[idx].init();
		Net_players[idx].reliable_socket = PSNET_INVALID_SOCKET;

		Net_players[idx].s_info.xfer_handle = -1;
		Net_players[idx].p_info.team = 0;
	}

	// initialize the Players array
	for (idx=0;idx<MAX_PLAYERS;idx++) {
		if (Player == &Players[idx]) {
			continue;
		}
		Players[idx].reset();
	}

	multi_vars_init();	

	// initialize the fake lag/loss system
#ifdef MULTI_USE_LAG
	multi_lag_init();
#endif

	// initialize the kick system
	multi_kick_init();

	// initialize all file xfer stuff
	multi_xfer_init(multi_file_xfer_notify);

	// close the chatbox (if one exists)
	chatbox_close();	

	// reset the data xfer system
	multi_data_reset();

	// initialize the voice system
	multi_voice_init();

	// intialize the pause system
	multi_pause_reset();

	// initialize endgame stuff
	multi_endgame_init();

	// initialize respawning
	multi_respawn_init();

	// initialize all netgame timestamps
	multi_reset_timestamps();

	// flush psnet sockets
	psnet_flush();
}

// multi_check_listen() calls low level psnet routine to see if we have a connection from a client we
// should accept.
void multi_check_listen()
{
	int i;
	net_addr addr;
	PSNET_SOCKET_RELIABLE sock;

	// call psnet routine which calls select to see if we need to check for a connect from a client
	// by passing addr, we are telling check_for_listen to do the accept and return who it was from in
	// addr.  The
	sock = psnet_rel_check_for_listen(&addr);

	if (sock != PSNET_INVALID_SOCKET) {
		// be sure that my address and the server address are set correctly.
		if ( !psnet_same(&Psnet_my_addr, &Net_player->p_info.addr) ){
			Net_player->p_info.addr = Psnet_my_addr;
		}

		if ( !psnet_same(&Psnet_my_addr, &(Netgame.server_addr)) ){
			Netgame.server_addr = Psnet_my_addr;
		}

		// the connection was accepted in check_for_listen.  Find the netplayer whose address we connected
		// with and assign the socket descriptor.
		// Updated to utilize psnet_same() for address comparison so the port is also taken into account.
		// This allows multiple players using NAT to access a remote server simultneously.
		for (i = 0; i < MAX_PLAYERS; i++ ) {
			if ( (Net_players[i].flags & NETINFO_FLAG_CONNECTED) && (psnet_same(&addr, &(Net_players[i].p_info.addr))) ) {
				// mark this flag so we know he's "fully" connected
				Net_players[i].flags |= NETINFO_FLAG_RELIABLE_CONNECTED;
				Net_players[i].reliable_socket = sock;

				// send player information to the joiner
				send_accept_player_data( &Net_players[i], (Net_players[i].flags & NETINFO_FLAG_INGAME_JOIN)?1:0 );

				// send a netgame update so the new client has all the necessary settings
				send_netgame_update_packet();	

				// if this is a team vs. team game, send an update
				if(Netgame.type_flags & NG_TYPE_TEAM){
					multi_team_send_update();
				}

				// NETLOG
				ml_printf(NOX("Accepted TCP connection from %s"), Net_players[i].m_player == NULL ? NOX("Unknown") : Net_players[i].m_player->callsign);				
				break;
			}
		}

		// if we didn't find a player, close the socket
		if ( i == MAX_PLAYERS ) {
			nprintf(("Network", "Got accept on my listen socket, but unknown player.  Closing socket.\n"));
			psnet_rel_close_socket(sock);
		}
	}
}

// returns true is server hasn't been heard from in N seconds. false otherwise
int multi_client_server_dead()
{
	fix this_time, last_time, max;

	// get the last time we have heard from the server.  If greater than some default, then maybe
	// display some icon on the HUD indicating slow network connection.  if greater than some higher
	// max, stop simulating on the client side until we hear from the server again.
	this_time = timer_get_fixed_seconds();
	last_time = Netgame.server->last_heard_time;
	// check for wrap!  must return 0
	if ( last_time > this_time )
		return 0;

	this_time -= last_time;

	// if in mission, use the smaller timeout value.  Outside of mission, use a large one.
	if ( MULTI_IN_MISSION ){
		max = MULTI_SERVER_MAX_TIMEOUT;
	} else {
		max = MULTI_SERVER_MAX_TIMEOUT_LARGE;
	}

	if ( this_time > max){
		return 1;
	} else {
		return 0;
	}
}

void multi_process_incoming();		// prototype for function later in this module

// function to process network data in hopes of getting info back from server
int multi_client_wait_on_server()
{
	int is_dead;

	is_dead = multi_client_server_dead();

	// if the server is back alive, tell our popup
	if ( !is_dead ){
		return MULTI_SERVER_ALIVE;
	}

	// on release version -- keep popup active for 60 seconds, then bail
#ifdef NDEBUG
	fix this_time = timer_get_fixed_seconds();
	// if the timer wrapped:
	if ( this_time < Multi_server_wait_start ) {
		Multi_server_wait_start = timer_get_fixed_seconds();
		return FALSE;
	}
	// check to see if timeout expired
	this_time -= Multi_server_wait_start;
	if ( this_time > MULTI_SERVER_WAIT_TIME ){
		return MULTI_SERVER_GONE;
	}
#endif

	return FALSE;
}

// function called by multiplayer clients to stop simulating when they have not heard from the server
// in a while.
void multi_client_check_server()
{
	int rval;

	Assert( MULTIPLAYER_CLIENT );	

	// this function can get called while in the popup code below.  So we include this check as a
	// reentrancy check.
	if ( Multi_server_check_count )
		return;

	// make sure we have a valid server
	if(Netgame.server == NULL){
		return;
	}

	Multi_server_check_count++;
	if(multi_client_server_dead()){
		Netgame.flags |= NG_FLAG_SERVER_LOST;
	} else {
		Netgame.flags &= ~(NG_FLAG_SERVER_LOST);
	}

	if(Netgame.flags & NG_FLAG_SERVER_LOST) {
		if(!(Game_mode & GM_IN_MISSION) && !popup_active()){	
			// need to start a popup
			Multi_server_wait_start = timer_get_fixed_seconds();
			rval = popup_till_condition( multi_client_wait_on_server, XSTR("Cancel",641), XSTR("Contact lost with server.  Stopping simulation until contact reestablished.  Press Cancel to exit game.",642) );
			
			if ( !rval || (rval == MULTI_SERVER_GONE) ) {				
				multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_CONTACT_LOST);								
			}
			Netgame.flags &= ~(NG_FLAG_SERVER_LOST);
		}
	}

	Multi_server_check_count--;
}


// -------------------------------------------------------------------------------------------------
//	process_packet_normal() will determine what sort of packet it is, and send it to the appropriate spot.
//	Prelimiary verification of the magic number and checksum are done here.  
//

void process_packet_normal(ubyte* data, header *header_info, bool reliable)
{
	// this is for helping to diagnose misaligned packets.  The last sensible packet 
	// is usually the culprit that needs to be analyzed.
	if (Cmdline_dump_packet_type) {
		mprintf(("Game packet type of %d received.\n", data[0]));
	}

	switch ( data[0] ) {

		case JOIN:
			process_join_packet(data, header_info);
			break;

		case GAME_CHAT:
			process_game_chat_packet( data, header_info );
			break;

		case NOTIFY_NEW_PLAYER:
			process_new_player_packet(data, header_info);
			break;

		case HUD_MSG:
			process_hud_message(data, header_info);
			break;

		case MISSION_MESSAGE:
			process_mission_message_packet( data, header_info );
			break;

		case LEAVE_GAME:
			process_leave_game_packet(data, header_info);
			break;

		case GAME_QUERY:
			process_game_query(data, header_info);
			break;

		case GAME_ACTIVE:
			process_game_active_packet(data, header_info);
			break;

		case GAME_INFO:
			process_game_info_packet( data, header_info );
			break;		

		case SECONDARY_FIRED_AI:
			process_secondary_fired_packet(data, header_info, 0);
			break;		

		case SECONDARY_FIRED_PLR:
			process_secondary_fired_packet(data, header_info, 1);
			break;

		case COUNTERMEASURE_FIRED:
			process_countermeasure_fired_packet( data, header_info );
			break;		

		case FIRE_TURRET_WEAPON:
			process_turret_fired_packet( data, header_info );
			break;

		case GAME_UPDATE:
			process_netgame_update_packet( data, header_info );
			break;

		case UPDATE_DESCRIPT:
			process_netgame_descript_packet( data, header_info );
			break;

		case NETPLAYER_UPDATE:
			process_netplayer_update_packet( data, header_info );
			break;

		case ACCEPT :
			process_accept_packet(data, header_info);
			break;				

		case OBJECT_UPDATE:
			multi_oo_process_update(data, header_info);
			break;

		case SHIP_KILL:
			process_ship_kill_packet( data, header_info );
			break;

		case MISSILE_KILL:
			process_weapon_kill_packet(data, header_info);
			break;

		case WING_CREATE:
			process_wing_create_packet( data, header_info );
			break;
			
		case SHIP_CREATE:
			process_ship_create_packet( data, header_info );
			break;

		case SHIP_DEPART:
			process_ship_depart_packet( data, header_info );
			break;

		case MISSION_LOG_ENTRY:
			process_mission_log_packet( data, header_info );
			break;		

		case PING:
			process_ping_packet(data, header_info);
			break;

		case PONG:
			process_pong_packet(data, header_info);
			break;		

		case XFER_PACKET:
			Assert(header_info->id >= 0);
			int np_index;
			PSNET_SOCKET_RELIABLE sock;
			sock = PSNET_INVALID_SOCKET;

			// if I'm the server of the game, find out who this came from			
			if((Net_player != NULL) && (Net_player->flags & NETINFO_FLAG_AM_MASTER)){
				np_index = find_player_index(header_info->id);
				if(np_index >= 0){
					sock = Net_players[np_index].reliable_socket;
				}
			}
			// otherwise always use my own socket
			else if(Net_player != NULL){
				sock = Net_player->reliable_socket;
			}
			
			header_info->bytes_processed = multi_xfer_process_packet(data + HEADER_LENGTH, sock) + HEADER_LENGTH;
			break;

		case MISSION_REQUEST:
			process_mission_request_packet(data,header_info);
			break;

		case MISSION_ITEM:
			process_mission_item_packet(data,header_info);
			break;		

		case MULTI_PAUSE_REQUEST:
			process_multi_pause_packet(data, header_info);
			break;		

		case INGAME_NAK:
			process_ingame_nak(data, header_info);
			break;

		case SHIPS_INGAME_PACKET:
			process_ingame_ships_packet(data, header_info);
			break;

		case WINGS_INGAME_PACKET:
			process_ingame_wings_packet(data, header_info);
			break;

		case MISSION_END:
			process_endgame_packet(data, header_info);
			break;

		case FORCE_MISSION_END:
			process_force_end_mission_packet(data, header_info);
			break;
		
		case OBSERVER_UPDATE:
			process_observer_update_packet(data, header_info);
			break;

		case NETPLAYER_SLOTS_P:
			process_netplayer_slot_packet(data, header_info);
			break;

		case SHIP_STATUS_CHANGE:
			process_ship_status_packet(data, header_info);
			break;

		case PLAYER_ORDER_PACKET:
			process_player_order_packet(data, header_info);
			break;

		case INGAME_SHIP_UPDATE:
			process_ingame_ship_update_packet(data, header_info);
			break;

		case INGAME_SHIP_REQUEST:
			process_ingame_ship_request_packet(data, header_info);
			break;
		
		case FILE_SIG_INFO:
			process_file_sig_packet(data, header_info);
			break;

		case RESPAWN_NOTICE:
			multi_respawn_process_packet(data,header_info);			
			break;

		case SUBSYSTEM_DESTROYED:
			process_subsystem_destroyed_packet( data, header_info );
			break;

		case LOAD_MISSION_NOW :
			process_netplayer_load_packet(data, header_info);
			break;

		case FILE_SIG_REQUEST :
			process_file_sig_request(data, header_info);
			break;

		case JUMP_INTO_GAME:
			process_jump_into_mission_packet(data, header_info);
			break;		

		case CLIENT_REPAIR_INFO:
			process_repair_info_packet(data,header_info);
			break;

		case MISSION_SYNC_DATA:
			process_mission_sync_packet(data,header_info);
			break;

		case STORE_MISSION_STATS:
			process_store_stats_packet(data, header_info);
			break;

		case DEBRIS_UPDATE:
			process_debris_update_packet(data, header_info);
			break;		

		case SHIP_WSTATE_CHANGE:
			process_ship_weapon_change( data, header_info );
			break;

		case WSS_UPDATE_PACKET:
			process_wss_update_packet(data, header_info);
			break;

		case WSS_REQUEST_PACKET:
			process_wss_request_packet( data, header_info );
			break;	

		case FIRING_INFO:
			process_firing_info_packet( data, header_info );
			break;		

		case CARGO_REVEALED:
			process_cargo_revealed_packet( data, header_info);
			break;		

		case CARGO_HIDDEN:
			process_cargo_hidden_packet( data, header_info);
			break;

		case SUBSYS_CARGO_REVEALED:
			process_subsystem_cargo_revealed_packet( data, header_info);
			break;		

		case SUBSYS_CARGO_HIDDEN:
			process_subsystem_cargo_hidden_packet( data, header_info);
			break;		

		case MISSION_GOAL_INFO:
			process_mission_goal_info_packet(data, header_info);
			break;

		case KICK_PLAYER:
			process_player_kick_packet(data, header_info);
			break;

		case PLAYER_SETTINGS:
			process_player_settings_packet(data, header_info);
			break;

		case DENY:
			process_deny_packet(data, header_info);
			break;

		case POST_SYNC_DATA:
			process_post_sync_data_packet(data, header_info);
			break;

		case WSS_SLOTS_DATA:
			process_wss_slots_data_packet(data,header_info);
			break;

		case SHIELD_EXPLOSION:
			process_shield_explosion_packet( data, header_info );
			break;

		case PLAYER_STATS:
			process_player_stats_block_packet(data, header_info);
			break;

		case SLOT_UPDATE:
			process_pslot_update_packet(data,header_info);
			break;

		case AI_INFO_UPDATE:
			process_ai_info_update_packet( data, header_info );
			break;		

		case CAMPAIGN_UPDATE :
			multi_campaign_process_update(data,header_info);
			break;

		case CAMPAIGN_UPDATE_INGAME:
			multi_campaign_process_ingame_start(data,header_info);
			break;

		case VOICE_PACKET :
			multi_voice_process_packet(data,header_info);
			break;

		case TEAM_UPDATE :
			multi_team_process_packet(data,header_info);
			break;

		case ASTEROID_INFO:
			process_asteroid_info(data, header_info);
			break;		

		case HOST_RESTR_QUERY:
			process_host_restr_packet(data, header_info);
			break;

		case OPTIONS_UPDATE:
			multi_options_process_packet(data,header_info);
			break;

		case SQUADMSG_PLAYER:
			multi_msg_process_squadmsg_packet(data,header_info);
			break;

		case NETGAME_END_ERROR:
			process_netgame_end_error_packet(data,header_info);
			break;

		case COUNTERMEASURE_SUCCESS:
			process_countermeasure_success_packet( data, header_info );
			break;

		case CLIENT_UPDATE:
			process_client_update_packet(data, header_info);
			break;

		case COUNTDOWN:
			process_countdown_packet(data, header_info);
			break;

		case DEBRIEF_INFO:
			process_debrief_info( data, header_info );
			break;

		case ACCEPT_PLAYER_DATA:
			process_accept_player_data( data, header_info );
			break;				

		case HOMING_WEAPON_UPDATE:
			process_homing_weapon_info( data, header_info );
			break;		

		case EMP_EFFECT:
			process_emp_effect(data, header_info);
			break;

		case REINFORCEMENT_AVAIL:
			process_reinforcement_avail( data, header_info );
			break;

		case PRIMARY_FIRED_NEW:
			process_NEW_primary_fired_packet(data, header_info);
			break;

		case LINEAR_WEAPON_FIRED:
			process_non_homing_fired_packet(data, header_info);
			break;

		case ANIMATION_TRIGGERED:
			process_animation_triggered_packet(data, header_info);
			break;

		case COUNTERMEASURE_NEW:
			process_NEW_countermeasure_fired_packet(data, header_info);
			break;

		case BEAM_FIRED:
			process_beam_fired_packet(data, header_info);
			break;		
			
		case SW_STD_QUERY:
			process_sw_query_packet(data, header_info);
			break;

		case EVENT_UPDATE:
			process_event_update_packet(data, header_info);
			break;

		case VARIABLE_UPDATE:
			process_variable_update_packet(data, header_info);
			break;

		case OBJECT_UPDATE_NEW:			
			multi_oo_process_update(data, header_info);
			break;

		case WEAPON_DET:
			process_weapon_detonate_packet(data, header_info);
			break;

		case FLAK_FIRED:
			process_flak_fired_packet(data, header_info);
			break;

		case NETPLAYER_PAIN:
			process_player_pain_packet(data, header_info);
			break;

		case LIGHTNING_PACKET:
			process_lightning_packet(data, header_info);
			break;

		case BYTES_SENT:
			process_bytes_recvd_packet(data, header_info);
			break;

		case TRANSFER_HOST:
			process_host_captain_change_packet(data, header_info);
			break;

		case SELF_DESTRUCT:
			process_self_destruct_packet(data, header_info);
			break;

		case SEXP:
			process_sexp_packet(data, header_info);
			break; 

		case TURRET_TRACK:
			process_turret_tracking_packet(data, header_info);
			break;

		case LUA_DATA_PACKET:
			process_lua_packet(data, header_info, reliable);
			break;

		default:
			mprintf(("Received packet with unknown type %d\n", data[0] ));
			header_info->bytes_processed = 10000;
			break;

	} // end switch

	// Let's also dump the amount of data that we've processed so far.
	if (Cmdline_dump_packet_type) {
		mprintf(("Game packet ended.  Total amount of data processed from packet is %d.\n", header_info->bytes_processed));
	}
}



// Takes a bunch of messages, check them for validity,
// and pass them to multi_process_data. 
//  --------------------^
// this should be process_packet() I think, or with the new code
// process_tracker_packet() as defined in MultiTracker.[h,cpp]
void multi_process_bigdata(ubyte *data, int len, net_addr *from_addr, int reliable)
{
	int bytes_processed;
	int player_num;
	header header_info;
	ubyte *buf;	

	// the only packets we will process from an unknown player are GAME_QUERY, GAME_INFO, JOIN, PING, PONG, ACCEPT, and GAME_ACTIVE packets
	player_num = find_player(from_addr);		

	// find the player who sent the message and mark the last_heard time for this player
	// check to see if netplayer is null (it may be in cases such as getting lists of games from the tracker)
	if(player_num >= 0){
		Net_players[player_num].last_heard_time = timer_get_fixed_seconds();
	}

	// store fields that were passed along in the message
	// store header information that was captured from the network-layer header
	memcpy(header_info.addr, from_addr->addr, sizeof(header_info.addr));
	header_info.port = from_addr->port;	
	if(player_num >= 0){
		header_info.id = Net_players[player_num].player_id;
	} else {
		header_info.id = -1;
	}   

	bytes_processed = 0;

	// start off logging of packets by writing how many bytes of data we should get through.
	if (Cmdline_dump_packet_type) {
		mprintf(("Network packet with %d bytes of data received. ", len));
	}

	while( (bytes_processed >= 0) && (bytes_processed < len) )  {

      buf = &(data[bytes_processed]);

      const ubyte type = buf[0];

		// if its coming from an unknown source, there are only certain packets we will actually process
		if((player_num == -1) && !multi_is_valid_unknown_packet((ubyte)type)){
			// So let's log it, because we should probably at least be vaguely aware of the buggy behavior.
			mprintf(("Receiving unknown source packet of type %d that should have a source, aborting packet processing!\n", type));
			return ;
		}		

		// perform any special processing checks here		
		process_packet_normal(buf,&header_info, reliable != 0);
		 
		// MWA -- magic number was removed from header on 8/4/97.  Replaced with bytes_processed
		// variable which gets stuffed whenever a packet is processed.
		bytes_processed += header_info.bytes_processed;
	}

	// if this is not reliable data and we have a valid player
	if(Net_player != NULL){
		if(!MULTIPLAYER_MASTER && !reliable && (Game_mode & GM_IN_MISSION)){
			Net_player->cl_bytes_recvd += len;
		}
	}
}

// process all reliable socket details
void multi_process_reliable_details()
{
	int idx;
	int sock_status;

	// run reliable sockets
	psnet_rel_work();

	
	// server operations
	if ( MULTIPLAYER_MASTER ){
		// listen for new reliable socket connections
		multi_check_listen();		

		// check for any broken sockets and delete any players
		for(idx=0; idx<MAX_PLAYERS; idx++){
			// players who _should_ be validly connected
			if((idx != MY_NET_PLAYER_NUM) && MULTI_CONNECTED(Net_players[idx])){				
				// if this guy's socket is broken or disconnected, kill him
				sock_status = psnet_rel_get_status(Net_players[idx].reliable_socket);
				if((sock_status == RNF_UNUSED) || (sock_status == RNF_BROKEN) || (sock_status == RNF_DISCONNECTED)){
					ml_string("Shutting down rel socket because of disconnect!");
					delete_player(idx);
				}

				// if we're still waiting for this guy to connect on his reliable socket and he's timed out, boot him
				if(Net_players[idx].s_info.reliable_connect_time != -1){
					// if he's connected
					if(Net_players[idx].reliable_socket != PSNET_INVALID_SOCKET){
						Net_players[idx].s_info.reliable_connect_time = -1;
					} 
					// if he's timed out
					else if(((time(nullptr) - Net_players[idx].s_info.reliable_connect_time) > MULTI_RELIABLE_CONNECT_WAIT) && (Net_players[idx].reliable_socket == PSNET_INVALID_SOCKET)){
						ml_string("Player timed out while connecting on reliable socket!");
						delete_player(idx);
					}
				}			
			}
		}
	}	
	// clients should detect broken sockets
	else {
		extern unsigned int Serverconn;
		if(Serverconn != 0xffffffff){
			int status = psnet_rel_get_status(Serverconn);
			if(status == RNF_BROKEN){
				mprintf(("CLIENT SOCKET DISCONNECTED\n"));

				// quit the game
				if(!multi_endgame_ending()){
					multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_CONTACT_LOST);
				}
			}
		}
	}
}

// multi_process_incoming reads incoming data off the unreliable and reliable ports and sends
// the data to process_big_data
void multi_process_incoming()
{
	int size;
	ubyte *data, *savep;
	net_addr from_addr;	

	Assert( Multi_read_count < NUM_REENTRANT_LEVELS );
	savep = net_buffer[Multi_read_count];

	Multi_read_count++;

	data = savep;

	// get the other net players data
	while( (size = psnet_get(data, &from_addr))>0 )	{
		// ingame joiners will ignore UDP packets until they are have picked a ship and are in the mission
		if( (Net_player->flags & NETINFO_FLAG_INGAME_JOIN) && (Net_player->state != NETPLAYER_STATE_INGAME_SHIP_SELECT) ){
			nprintf(("Network","Tossing UDP like a good little ingame joiner...\n"));
		} 
		// otherwise process incoming data normally
		else {
			multi_process_bigdata(data, size, &from_addr, 0);
		}
	} // end while

	// read reliable sockets for data
	data = savep;
	int idx;

	if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
		for (idx=0;idx<MAX_PLAYERS;idx++) {
			if((Net_players[idx].flags & NETINFO_FLAG_CONNECTED) && (Net_player != NULL) && (Net_player->player_id != Net_players[idx].player_id)){
				while( (size = psnet_rel_get(Net_players[idx].reliable_socket, data, MAX_NET_BUFFER)) > 0){
					multi_process_bigdata(data, size, &Net_players[idx].p_info.addr, 1);
				}
			}
		}
	} else {
		// if I'm not the master of the game, read reliable data from my connection with the server
		if((Net_player->reliable_socket != PSNET_INVALID_SOCKET) && (Net_player->reliable_socket != 0)){
			while( (size = psnet_rel_get(Net_player->reliable_socket,data, MAX_NET_BUFFER)) > 0){				
				multi_process_bigdata(data, size, &Netgame.server_addr, 1);
			}
		}
	}
		
	Multi_read_count--;
}

// -------------------------------------------------------------------------------------------------
//	multi_do_frame() is called once per game loop do update all the multiplayer objects, and send
// the player data to all the other net players.
//
//

int eye_tog = 1;
DCF(eye_tog, "Toggles setting of the local player eyepoint on every frame (Multiplayer)")
{
	if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
		dc_printf("proper eye stuff is %s\n", eye_tog ? "ON" : "OFF");
		return;
	}

	eye_tog = !eye_tog;
	dc_printf("proper eye stuff is %s\n", eye_tog ? "ON" : "OFF");
}

void multi_do_frame()
{	
	PSNET_TOP_LAYER_PROCESS();

	// always set the local player eye position/orientation here so we know its valid throughout all multiplayer
	// function calls
	if((Net_player != NULL) && eye_tog){
		camid cid = player_get_cam();
		if(cid.isValid())
		{
			camera *cam = cid.getCamera();
			cam->get_info(&Net_player->s_info.eye_pos, &Net_player->s_info.eye_orient);
		}
	}

	// send all buffered packets from the previous frame
	multi_io_send_buffered_packets();

	// datarate tracking
	multi_rate_process();

	// always process any pending endgame details
	multi_endgame_process();		

	// process all reliable socket details, including :
	// 1.) Listening for new pending reliable connections (server)
	// 2.) Checking for broken sockets (server/client)
	// 3.) Checking for clients who haven't fully connected
	multi_process_reliable_details();	

	// get the other net players data
	multi_process_incoming();		

	// process object update datarate stuff (for clients and server both)
	multi_oo_rate_process();

	// clients should check when last time they heard from sever was -- if too long, then
	// pause the simulation so wait for it to possibly come back
	if ( (MULTIPLAYER_CLIENT) && (Net_player->flags & NETINFO_FLAG_CONNECTED) ){
		multi_client_check_server();
	}

	// everybody pings all the time	
	if((Next_ping_time < 0) || ((time(NULL) - Next_ping_time) > PING_SEND_TIME) ){
		if( (Net_player->flags & NETINFO_FLAG_AM_MASTER) ){
			send_netplayer_update_packet();
		}
		
		// ping everyone
		multi_ping_send_all();
		Next_ping_time = time(NULL);		
	}	
	
	// if I am the master, and we are not yet actually playing the mission, send off netgame
	// status to all other players in the game.  If I am not the master of the game, and we
	// are not in the game, then send out my netplayer status to the host
	if ( (Net_player->flags & NETINFO_FLAG_CONNECTED) && !(Game_mode & GM_IN_MISSION)){	
		if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {			
			if ( (Netgame_send_time < 0) || ((time(NULL) - Netgame_send_time) > NETGAME_SEND_TIME) ) {
				send_netgame_update_packet(nullptr, true);
				
				Netgame_send_time = (int) time(NULL);
			}		
		} else {
			if ( (State_send_time < 0) || ((time(NULL) - State_send_time) > STATE_SEND_TIME) ){
				// observers shouldn't send an update state packet
				if ( !(Net_player->flags & NETINFO_FLAG_OBSERVER) ){
					send_netplayer_update_packet();
				}				
				
				State_send_time = time(NULL);
			}
		}
	}
	else if ( (Net_player->flags & NETINFO_FLAG_CONNECTED) && (Game_mode & GM_IN_MISSION) ) {	
		// if I am connected and am in the mission, do things that need to be done on a regular basis
		if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
			if ( (Gameinfo_send_time < 0) || ((time(NULL) - Gameinfo_send_time) > GAMEINFO_SEND_TIME)){
				send_game_info_packet();
				
				Gameinfo_send_time = (int) time(NULL);
			}
			
			// for any potential respawns
			multi_respawn_handle_invul_players();
			multi_respawn_check_ai();

			// for any potential ingame joiners
			multi_handle_ingame_joiners();
		} else {
			// the clients need to do some processing of stuff as well			
		}
	}

	// check to see if we're waiting on confirmation for a restricted ingame join
	if(Multi_restr_query_timestamp.isValid()){
		// if it has elapsed, unset the ingame join flag
		if(ui_timestamp_elapsed(Multi_restr_query_timestamp)){
			Multi_restr_query_timestamp = UI_TIMESTAMP::invalid();
			Netgame.flags &= ~(NG_FLAG_INGAME_JOINING);		
		}	
	}

	// while in the mission, send my PlayerControls to the host so that he can process
	// my movement
	if ( Game_mode & GM_IN_MISSION ) {

		if ( !(Net_player->flags & NETINFO_FLAG_AM_MASTER)){					
			if(Net_player->flags & NETINFO_FLAG_OBSERVER){
				// if the rate limiting system says its ok
				if(multi_oo_cirate_can_send()){
					// send my observer position/object update
					send_observer_update_packet();
				}
			} else if ( !(Player_ship->is_departing() ) ){
				// if the rate limiting system says its ok
				if(multi_oo_cirate_can_send()){
					// use the new method
					multi_oo_send_control_info();
				}				
			}

			// bytes received info
			if( (Next_bytes_time < 0) || ((time(NULL) - Next_bytes_time) > BYTES_SENT_TIME) ){
				if(Net_player != NULL){
					send_bytes_recvd_packet(Net_player);

					// reset bytes recvd
					Net_player->cl_bytes_recvd = 0;
				}

				// reset timestamp
				Next_bytes_time = (int) time(NULL);				
			}
		} else {

			// right before sending new positions, we should do any rollback shots and resimulation
			multi_ship_record_do_rollback();

			// sending new objects from here is dependent on having objects only created after
			// the game is done moving the objects.  I think that I can enforce this.				
			multi_oo_process();

			// send updates for turret tracking.
			Multi_Turret_Manager.send_queued_packets();			

			// evaluate whether the time limit has been reached or max kills has been reached
			// Commented out by Sandeep 4/12/98, was causing problems with testing.
			if( ((f2fl(Netgame.options.mission_time_limit) > 0.0f) && (Missiontime > Netgame.options.mission_time_limit)) ||
				 multi_kill_limit_reached() ) {

				// make sure we don't do this more than once
				if(Netgame.game_state == NETGAME_STATE_IN_MISSION){				
					multi_handle_end_mission_request();									
				}
			}			
		}			
	}

	// periodically send a client update packet to all clients
	if((Net_player != NULL) && (Net_player->flags & NETINFO_FLAG_AM_MASTER)){
		int idx;
		for(idx=0;idx<MAX_PLAYERS;idx++){
			if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx])){
				if ( !Multi_client_update_times[idx].isValid() || ui_timestamp_elapsed_safe(Multi_client_update_times[idx], MILLISECONDS_PER_SECOND) ) {
					
					send_client_update_packet(&Net_players[idx]);
					
					Multi_client_update_times[idx] = ui_timestamp(Multi_client_update_intervals[Net_players[idx].p_info.options.obj_update_level]);
				}
			}
		}
	}	


	// process any kicked player details
	multi_kick_process();

	// do any file xfer details
	multi_xfer_do();

	// process any player data details (wav files, pilot pics, etc)
	multi_data_do();

	// do any voice details
	multi_voice_process();

	// process any player messaging details
	multi_msg_process();		
	
	// process any tracker messages
	multi_fs_tracker_process();

	// Cyborg17 update the new frame recording system for accurate client shots, needs to go after most everything else in multi.
	if (Game_mode & GM_IN_MISSION) {
		multi_ship_record_increment_frame();
	}

	// if on the standalone, do any gui stuff
	if (Game_mode & GM_STANDALONE_SERVER) {
		std_do_gui_frame();
	}	

	// if master then maybe do port forwarding setup/refresh/wait
	if (Net_player->flags & NETINFO_FLAG_AM_MASTER) {
		multi_port_forward_do();

		// do mdns stuff here too
		if ( !MULTI_IS_TRACKER_GAME && (Net_player->p_info.options.flags & MLO_FLAG_LOCAL_BROADCAST) ) {
			multi_mdns_service_do();
		}
	}
}

// -------------------------------------------------------------------------------------------------
//	multi_pause_do_frame() is called once per game loop do update all the multiplayer objects, and send
// the player data to all the other net players when the multiplayer game is paused. It only will do 
// checking for a few specialized packets (MULTI_UNPAUSE, etc)
//

void multi_pause_do_frame()
{
	PSNET_TOP_LAYER_PROCESS();

	// always set the local player eye position/orientation here so we know its valid throughout all multiplayer
	// function calls
	// if((Net_player != NULL) && eye_tog){
		// player_get_eye(&Net_player->s_info.eye_pos, &Net_player->s_info.eye_orient);
	// }

	// send all buffered packets from the previous frame
	multi_io_send_buffered_packets();

	// always process any pending endgame details
	multi_endgame_process();		

	// process all reliable socket details, including :
	// 1.) Listening for new pending reliable connections (server)
	// 2.) Checking for broken sockets (server/client)
	// 3.) Checking for clients who haven't fully connected
	multi_process_reliable_details();	

	// these timestamps and handlers shoul be evaluated in the pause state
	if ( Net_player->flags & NETINFO_FLAG_AM_MASTER ) {
		if ( (Gameinfo_send_time < 0) || ((time(NULL) - Gameinfo_send_time) > GAMEINFO_SEND_TIME) ){
			send_game_info_packet();
			
			Gameinfo_send_time = (int) time(NULL);
		}				
	}

	// everybody pings all the time
	if((Next_ping_time < 0) || ((time(NULL) - Next_ping_time) > PING_SEND_TIME) ){
		multi_ping_send_all();
		
		Next_ping_time = time(NULL);
	}

	// periodically send a client update packet to all clients
	if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
		int idx;

		for(idx=0;idx<MAX_PLAYERS;idx++){
			if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx])){			
				if ( !Multi_client_update_times[idx].isValid() || ui_timestamp_elapsed_safe(Multi_client_update_times[idx], MILLISECONDS_PER_SECOND) ) {
					
					send_client_update_packet(&Net_players[idx]);
					
					Multi_client_update_times[idx] = ui_timestamp(Multi_client_update_intervals[Net_players[idx].p_info.options.obj_update_level]);
				}
			}				
		}

		// for any potential ingame joiners
		multi_handle_ingame_joiners();
	}	

	// do any file xfer details
	multi_xfer_do();

	// process any player data details (wav files, pilot pics, etc)
	multi_data_do();

	// get the other net players data
	multi_process_incoming();	

	// do any voice details
	multi_voice_process();

	// process any player messaging details
	multi_msg_process();

	// process any kicked player details
	multi_kick_process();

	// process any pending endgame details
	multi_endgame_process();

	// process object update stuff (for clients and server both)
	if(MULTIPLAYER_MASTER){
		multi_oo_process();
	}

	// if on the standalone, do any gui stuff
	if (Game_mode & GM_STANDALONE_SERVER) {
		std_do_gui_frame();
	}

	// if master then maybe do port forwarding setup/refresh/wait
	if (Net_player->flags & NETINFO_FLAG_AM_MASTER) {
		multi_port_forward_do();

		// do mdns stuff here too
		if ( !MULTI_IS_TRACKER_GAME && (Net_player->p_info.options.flags & MLO_FLAG_LOCAL_BROADCAST) ) {
			multi_mdns_service_do();
		}
	}
}




// --------------------------------------------------------------------------------
// standalone_main_init()  the standalone equivalent of the main menu
//

extern int sock_inited;
float frame_time = (float)1.0/(float)30.0;

void standalone_main_init()
{
   std_debug_set_standalone_state_string("Main Init");   

	Game_mode = (GM_STANDALONE_SERVER | GM_MULTIPLAYER);	

	// NETLOG
	ml_string(NOX("Standalone server initializing"));

	// read in config file
	// multi_options_read_config();   
#ifdef _WIN32
	// if we failed to startup on our desired protocol, fail
	if ( !psnet_is_active() ) {
		if (Psnet_failure_code == WSAEADDRINUSE) {
			os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR, XSTR("You have selected TCP/IP for multiplayer FreeSpace, but the TCP socket is already in use.  Check for another instance and/or use the \"-port <port_num>\" command line option to select an available port.", 1604));
		}
		else {
			os::dialogs::Message(os::dialogs::MESSAGEBOX_ERROR, XSTR("You have selected TCP/IP for multiplayer FreeSpace, but the TCP/IP protocol was not detected on your machine.", 362));
		}

		exit(1);
	}
#endif // ifdef _WIN32

	// clear out the Netgame structure and start filling in the values
	// NOTE : these values are not incredibly important since they will be overwritten by the host when he joins
	Netgame.init();
	Netgame.game_state = NETGAME_STATE_FORMING;		// game is currently starting up
	Netgame.security = 0;
	Netgame.server_addr = Psnet_my_addr;

	The_mission.Reset( );
		
	// reinitialize all systems	
	multi_level_init();	

	// intialize endgame stuff
	multi_endgame_init();

	// clear the file xfer system
	multi_xfer_reset();
	multi_xfer_force_dir(CF_TYPE_MULTI_CACHE);

	// setup a blank pilot (this is a standalone usage only!)
	Pilot.load_player(NULL);

	// setup the netplayer for the standalone
	Net_player = &Net_players[0];	
	Net_player->tracker_player_id = -1;
	Net_player->flags |= (NETINFO_FLAG_AM_MASTER | NETINFO_FLAG_CONNECTED | NETINFO_FLAG_DO_NETWORKING | NETINFO_FLAG_MISSION_OK);
	Net_player->state = NETPLAYER_STATE_WAITING;
	Net_player->m_player = Player;
	strcpy_s(Player->callsign, "server");
	Net_player->p_info.addr = Psnet_my_addr;
	Net_player->s_info.xfer_handle = -1;	
	Net_player->player_id = multi_get_new_id();	
	Netgame.server = Net_player; 

	// maybe flag the game as having a hacked ships.tbl
	/*if(!Game_ships_tbl_valid){
		Netgame.flags |= NG_FLAG_HACKED_SHIPS_TBL;
	}
	// maybe flag the game as having a hacked weapons.tbl
	if(!Game_weapons_tbl_valid){
		Netgame.flags |= NG_FLAG_HACKED_WEAPONS_TBL;
	}*/

	// hacked data
	if(game_hacked_data()){
		Netgame.flags |= NG_FLAG_HACKED_SHIPS_TBL;
		Net_player->flags |= NETINFO_FLAG_HAXOR;
	}

	// setup debug flags
	Netgame.debug_flags = 0;

	// setup the default game name for the standalone
	std_connect_set_gamename(NULL);

	// set netgame default options
	multi_options_set_netgame_defaults(&Netgame.options);

	// set local netplayer default options
	multi_options_set_local_defaults(&Net_player->p_info.options);

	// set our object update level from the standalone default	
	Net_player->p_info.options.obj_update_level = Multi_options_g.std_datarate;
	switch(Net_player->p_info.options.obj_update_level){
	case OBJ_UPDATE_LOW:
		nprintf(("Network","STANDALONE USING LOW UPDATES\n"));
		break;
	case OBJ_UPDATE_MEDIUM:
		nprintf(("Network","STANDALONE USING MEDIUM UPDATES\n"));
		break;
	case OBJ_UPDATE_HIGH:
		nprintf(("Network","STANDALONE USING HIGH UPDATE\n"));
		break;
	case OBJ_UPDATE_LAN:
		nprintf(("Network","STANDALONE USING LAN UPDATE\n"));
		break;
	}

	// clear out various things
	animation::ModelAnimationParseHelper::parseTables();
	psnet_flush();
	game_flush();
	virtual_pof_init();
	ship_init();

	// setup port forwarding
	multi_port_forward_init();

	// setup mdns
	if ( !MULTI_IS_TRACKER_GAME ) {
		multi_mdns_service_init();
	}

	// login to game tracker
	std_tracker_login();

	std_debug_set_standalone_state_string("Main Do");
	std_set_standalone_fps((float)0);
	std_multi_set_standalone_missiontime((float)0);

	// load my missions and campaigns
	multi_create_list_load_missions();
	multi_create_list_load_campaigns();

	// if this is a tracker game, validate missions
	if(MULTI_IS_TRACKER_GAME){
		multi_update_valid_missions();
	}
}


// --------------------------------------------------------------------------------
// standalone_main_do()
//

// DESCRIPTION : the standalone server will wait in this state until the host of the game 
//               is "Waiting". That is, his state==NETPLAYER_STATE_WAITING, and he has finished
//               doing everything and wants to play the game. Once this happens, we will jump
//               into GS_STATE_MULTI_SERVER_WAIT

void standalone_main_do()
{
 
   os_sleep(10);  // since nothing will really be going on here, we can afford to give some time
               // back to the operating system.

	// kind of a do-nothing spin state.
	// The standalone will eventually move into the GS_STATE_MULTI_MISSION_SYNC state when a host connects and
	// attempts to start a game

   // process/renew port mapping
   multi_port_forward_do();

	// handle mdns messages
	if ( !MULTI_IS_TRACKER_GAME ) {
		multi_mdns_service_do();
	}
}

// --------------------------------------------------------------------------------
// standalone_main_close()
//

void standalone_main_close()
{
   std_debug_set_standalone_state_string("Main Close");

	// disconnect game from tracker
	multi_fs_tracker_logout();

	// remove port forwarding
	multi_port_forward_close();

	// stop mdns
	multi_mdns_service_close();
}

void multi_standalone_reset_all()
{	
	int idx;

	// NETLOG
	ml_string(NOX("Standalone resetting"));
	
	// shut all game stuff down
	game_level_close();

	// reinitialize the gui
	std_reset_standalone_gui();	

	// close down all sockets
	for(idx=0;idx<MAX_PLAYERS;idx++){

		// 6/25/98 -- MWA -- call delete_player here to remove the player.  This closes down the socket
		// and marks the player as not connected anymore.  It is probably cleaner to do this.
		if ( &Net_players[idx] != Net_player ) {
			delete_player( idx );
		}		
	}

	// make sure we go to the proper state.	
	if(gameseq_get_state() == GS_STATE_STANDALONE_MAIN){
		standalone_main_init();
	}
	gameseq_post_event(GS_EVENT_STANDALONE_MAIN);	
}

// --------------------------------------------------------------------------------
// multi_server_wait_init()  do stuff like setting the status bits correctly
//

void multi_standalone_wait_init()
{	
	std_debug_set_standalone_state_string("Wait Do");
	std_multi_add_goals();   // fill in the goals for the mission into the tree view
	multi_reset_timestamps();

	// create the bogus standalone object
	multi_create_standalone_object();
}


// --------------------------------------------------------------------------------
// multi_server_wait_do_frame() wait for everyone to log in or the host to send commands
// 

// DESCRIPTION : we will be in this state once the host of the game is waiting for everyone
//               to be finished and ready to go, at which point, we will will tell everyone
//               to enter the game, and we will start simulating ourselves. Note that most of
//               this code is lifted from multi_wait_do_frame()
void multi_standalone_wait_do()
{
}

// --------------------------------------------------------------------------------
// multi_server_wait_close() cleanup
//

void multi_standalone_wait_close()
{
	std_debug_set_standalone_state_string("Wait Close / Game Play");
	
	// all players should reset sequencing
	int idx;
	for(idx=0;idx<MAX_PLAYERS;idx++){
		if(Net_player->flags & NETINFO_FLAG_CONNECTED){
			Net_players[idx].client_cinfo_seq = 0;
			Net_players[idx].client_server_seq = 0;	
		}
	}
}


// this is an artificial state which will push the standalone into the main state without it having to go through 
// the init function (which would disconnect everyone and generally just screw things up)
// it will also eventually do tracker stats update
extern int Multi_debrief_server_framecount;
void multi_standalone_postgame_init()	
{
	std_debug_set_standalone_state_string("Postgame / Send Stats");

	// NETLOG
	ml_string(NOX("Standlone entering postgame"));

	mission_goal_fail_incomplete();

	// handle campaign stuff
	if ( Game_mode & GM_CAMPAIGN_MODE ) {
		// MUST store goals and events first - may be used to evaluate next mission
		// store goals and events
		mission_campaign_store_goals_and_events_and_variables();

		// evaluate next mission
		mission_campaign_eval_next_mission();
	}	

	// always set my state to be "DEBRIEF_ACCEPT"
	Net_player->state = NETPLAYER_STATE_DEBRIEF_ACCEPT;	

	// mark stats as not being store yet
	Netgame.flags &= ~(NG_FLAG_STORED_MT_STATS);

	Multi_debrief_server_framecount = 0;

	// reset network timestamps
	multi_reset_timestamps();
}

void multi_standalone_postgame_do()
{
	// wait until everyone is in the debriefing
	if((Netgame.game_state != NETGAME_STATE_DEBRIEF) && multi_netplayer_state_check(NETPLAYER_STATE_DEBRIEF, 1)){		
		Netgame.game_state = NETGAME_STATE_DEBRIEF;
		send_netgame_update_packet();
		debrief_multi_server_stuff();
	}
	
	// process server debriefing details
	if(Netgame.game_state == NETGAME_STATE_DEBRIEF){
		multi_debrief_server_process();
	}
}

void multi_standalone_postgame_close()
{
	// maybe store stats on tracker
	if ( MULTI_IS_TRACKER_GAME && !(Netgame.flags & NG_FLAG_STORED_MT_STATS) ) {
		if (multi_debrief_stats_accept_code() != 0) {
			int stats_saved = multi_fs_std_tracker_store_stats();

			if (stats_saved) {
				Netgame.flags |= NG_FLAG_STORED_MT_STATS;
				send_netgame_update_packet();
			} else {
				send_store_stats_packet(0);
			}

			if (Netgame.type_flags & NG_TYPE_SW) {
				multi_sw_report(stats_saved);
			}
		}
	}
}


void multi_reset_timestamps()
{
	int i;

	for ( i = 0 ; i < MAX_PLAYERS; i++ ){
		Multi_client_update_times[i] = UI_TIMESTAMP::invalid();
	}
	Netgame_send_time = -1;
	Gameinfo_send_time = -1;	
	Next_ping_time = -1;
	State_send_time = -1;
	Next_bytes_time = -1;

	chatbox_reset_timestamps();

	// do for all players so that ingame joiners work properly.
	for (i = 0; i < MAX_PLAYERS; i++ ) {
		Players[i].update_dumbfire_time = timestamp(0);
		Players[i].update_lock_time = timestamp(0);

		Net_players[i].s_info.voice_token_timestamp = UI_TIMESTAMP::invalid();
		Net_players[i].s_info.player_collision_timestamp = TIMESTAMP::immediate();
	}

	// reset standalone gui timestamps (these are not game critical, so there is not much danger)
	std_reset_timestamps();
}

// netgame debug flags for debug console stuff
DCF(netd, "change netgame debug flags (Mulitplayer)")
{
	int value;
	dc_stuff_int(&value);
	
	// if we're the server, change flags
	if ((Net_player != NULL) && (Net_player->flags & NETINFO_FLAG_AM_MASTER) && (value <= 7)) {
		Netgame.debug_flags ^= (1 << value);
	}

	// display network flags
	dc_printf("BITS\n");
}

// display any multiplayer/networking information here
void multi_display_netinfo()
{
	int sx = gr_screen.center_offset_x + gr_screen.center_w - 200;
	int sy = gr_screen.center_offset_y + 20;
	int dy = gr_get_font_height() + 1;
	int idx;

	// not multiplayer
	if(!(Game_mode & GM_MULTIPLAYER)){
		return;
	}

	// HUD is turned off
	if (!HUD_draw) {
		return;
	}

	// message window is open
	if (Player->flags & PLAYER_FLAGS_MSG_MODE) {
		return;
	}

	gr_set_color_fast(&Color_normal);

	// server or client
	if(MULTIPLAYER_MASTER){
		gr_string(sx, sy, "SERVER", GR_RESIZE_NONE); sy += dy;

		for(idx=0; idx<MAX_PLAYERS; idx++){
			if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx]) && (Net_players[idx].m_player != NULL)){
				if(Net_players[idx].sv_last_pl < 0){
					gr_printf_no_resize(sx, sy, "%s: %d, %d pl", Net_players[idx].m_player->callsign, Net_players[idx].sv_bytes_sent, 0);
					sy += dy;
				} else {
					gr_printf_no_resize(sx, sy, "%s: %d, %d pl", Net_players[idx].m_player->callsign, Net_players[idx].sv_bytes_sent, Net_players[idx].sv_last_pl);
					sy += dy;
				}
			}
		}
	} else {
		gr_string(sx, sy, "CLIENT", GR_RESIZE_NONE); sy += dy;

		// display PL
		if(Net_player != NULL){
			if(Net_player->cl_last_pl < 0){
				gr_printf_no_resize(sx, sy, "PL: %d %d pl\n", Net_player->cl_bytes_recvd, 0);
				sy += dy;
			} else {
				gr_printf_no_resize(sx, sy, "PL: %d %d pl\n", Net_player->cl_bytes_recvd, Net_player->cl_last_pl);
				sy += dy;
			}
		}
	}
}
