Files
fs2open.github.com/code/network/multi.cpp
T
Goober5000 1b51225c51 convert the FSO timestamp system to use SDL time
Previously, the game used a dead-reckoning system for measuring time, where several time counters were incremented as long as time was running.  This unfortunately has no way to synchronize time in the long term, and therefore in-game time will tend to run faster or slower than real time by a few percent, depending on various factors.

This PR redesigns the timestamp system to use the SDL time counter which will always keep it in sync with real time.  However this necessarily involves a bit of clever handling when the game is paused and unpaused.  It also revealed several minor bugs in several places in the code where timestamps were handled.

This PR is currently in draft status until the design can be updated to handle time compression.  In all other respects it should be working.
2022-01-16 15:20:57 -05:00

1879 lines
53 KiB
C++

/*
* 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 "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 "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 "pilotfile/pilotfile.h"
#include "debugconsole/console.h"
#include "network/psnet2.h"
#include "network/multi_mdns.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
#define MULTI_CLIENT_UPDATE_TIME 333
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
active_game* Active_game_head; // linked list of active games displayed on Join screen
int Active_game_count; // for interface screens as well
CFILE* Multi_chat_stream; // for streaming multiplayer chat strings to a file
int Multi_has_cd = 0; // if this machine has a cd or not (call multi_common_verify_cd() to set this)
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
int 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
int 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
int 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;
int Multi_restr_query_timestamp = -1;
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
memset(Net_players, 0, sizeof(net_player) * MAX_PLAYERS);
for(idx=0; idx<MAX_PLAYERS; idx++){
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 = -1;
// respawn stuff
Multi_server_check_count = 0;
// reentrant variable
Multi_read_count = 0;
// unset the "have cd" var
// NOTE: we unset this here because we are going to be calling multi_common_verify_cd()
// immediately after this (in multi_level_init() to re-check the status)
Multi_has_cd = 0;
// current file checksum
Multi_current_file_checksum = 0;
Multi_current_file_length = -1;
Active_game_head = NULL;
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);
memset(&Net_players[idx],0,sizeof(net_player));
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)
{
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;
default:
nprintf(("Network", "Received packet with unknown type %d\n", data[0] ));
header_info->bytes_processed = 10000;
break;
} // end switch
}
// 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 type, 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;
while( (bytes_processed >= 0) && (bytes_processed < len) ) {
buf = &(data[bytes_processed]);
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)){
return ;
}
if ( (type<0) || (type > MAX_TYPE_ID )) {
nprintf( ("Network", "multi_process_bigdata: Invalid packet type %d!\n", type ));
return;
}
// perform any special processing checks here
process_packet_normal(buf,&header_info);
// 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 = (int) 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 = (int) 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 != -1){
// if it has elapsed, unset the ingame join flag
if(timestamp_elapsed(Multi_restr_query_timestamp)){
Multi_restr_query_timestamp = -1;
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();
// 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] < 0) || timestamp_elapsed_safe(Multi_client_update_times[idx], 1000)){
send_client_update_packet(&Net_players[idx]);
Multi_client_update_times[idx] = timestamp(MULTI_CLIENT_UPDATE_TIME);
}
}
}
}
// 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 = (int) 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] < 0) || timestamp_elapsed_safe(Multi_client_update_times[idx], 1000)){
send_client_update_packet(&Net_players[idx]);
Multi_client_update_times[idx] = timestamp(MULTI_CLIENT_UPDATE_TIME);
}
}
}
// 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
memset( &Netgame, 0, sizeof(Netgame) );
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();
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] = -1;
}
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 = -1;
Net_players[i].s_info.player_collision_timestamp = timestamp(0);
}
// 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;
}
}
}
}