Files
Cliff GordonandClaude Opus 4.6 cfd9e0087c Fix multiplayer standalone crash: client stats race + host accept code (#7319)
* Fix multiplayer standalone crash: client stats race + host accept code

Fixes a crash in multiplayer PXO/tracker games hosted on a standalone
server where clients crash at debrief close after scoring a kill:

  pilotfile.cpp:295: Ship kills of '<ship class>' not found,
  should have been added by pilotfile::update_stats

Two independent root causes were found, both of which must be fixed for
correct behavior:

--- Root cause 1: race condition in multi_sexp_end_mission() (sexp.cpp) ---

The previous code called send_debrief_event() before
multi_handle_end_mission_request() as a "hack" to skip the warp-out
sequence when an end-mission SEXP fires. On standalone servers this is
a race condition: scoring_eval_kill only runs on the master, and the
master hasn't broadcast m_okKills to clients yet at the point
multi_sexp_end_mission() is called. Entering debrief this early causes
scoring_level_close() to run with zeroed m_okKills, so
Pilot.update_stats() creates no kill entries for that session.
When debrief_close() later calls scoring_backout_accept() it cannot
find those entries and hits the UNREACHABLE.

Fix: Remove the send_debrief_event() call entirely. The normal flow
via multi_handle_end_mission_request() has the server broadcast stats
first and then send MISSION_END, which causes the client to enter
debrief with correct kill data already populated. The master enters
debrief through sexp_end_mission() -> send_debrief_event() as before.

The behavioral side-effect of removing this hack: clients that go
through the normal warp-out sequence will no longer skip it on
end-mission. Clients in a state where they cannot warp still receive
immediate debrief via multi_handle_sudden_mission_end().

--- Root cause 2: host accept code never set on PXO standalone (multiui.cpp) ---

In multi_debrief_accept_hit() and multi_debrief_esc_hit(), when the
game is a PXO/tracker game and the local player is the game host
(NETINFO_FLAG_GAME_HOST), the code calls send_store_stats_packet() to
tell clients to record their stats. It then sets
Multi_debrief_stats_accept_code = 1 via the per-client response path
inside the NETINFO_FLAG_AM_MASTER block.

On a standalone server the game host is NOT the master
(NETINFO_FLAG_AM_MASTER is false for the client-side host). The
NETINFO_FLAG_AM_MASTER block is skipped entirely, and
send_store_stats_packet() uses multi_io_send_to_all_reliable() which
skips the local player — so the host never receives its own packet.
Multi_debrief_stats_accept_code stays at -1 (unset).

When debrief_close() checks this value and sees it is not 1, it
assumes stats were tossed or the window was closed without accepting,
and runs scoring_backout_accept() unconditionally. With fix 1 in place
this no longer crashes (entries exist), but silently strips all session
kill credit from the host's pilot record.

Fix: After the send_store_stats_packet() block, call
multi_debrief_stats_accept() unconditionally for the game host so the
accept code is set locally regardless of whether the host is also the
standalone master.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* Preserve end-mission warp-out skip via deferred debrief entry

The previous fix removed send_debrief_event() from
multi_sexp_end_mission() entirely, which was correct for the stats
race condition but had a side effect: clients would no longer skip the
warp-out sequence when an end-mission SEXP fires.  The warp-out skip
was an intentional feature — process_endgame_packet() checks whether
the client is already in debrief state and, if so, bypasses
multi_warpout_all_players().

Restore the original behavior by deferring the send_debrief_event()
call to process_endgame_packet() rather than eliminating it.
multi_sexp_end_mission() now sets Multi_sexp_end_mission_pending=true
before calling multi_handle_end_mission_request().  When the client
receives the MISSION_END packet, process_endgame_packet() checks the
flag, calls send_debrief_event() there, then falls through to the
debrief-state check which skips the warp-out exactly as before.

This is safe because send_endgame_packet() calls multi_broadcast_stats()
before sending MISSION_END.  Both use reliable delivery, so stats are
guaranteed to be received and applied before MISSION_END is processed,
meaning scoring_level_close() now sees correct kill data when
send_debrief_event() fires.

Alt-J-initiated mission ends are unaffected: the flag is never set,
so the warp-out proceeds normally for those paths.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* Revert multiui.cpp accept code change — tracker master owns stats

The previous change added multi_debrief_stats_accept() calls for the
game host in multi_debrief_accept_hit() and multi_debrief_esc_hit() to
work around Multi_debrief_stats_accept_code staying -1 on a standalone
server.  This was based on incorrect analysis.

In PXO tracker games the standalone master is intentionally the sole
authority for stat storage (via multi_fs_std_tracker_store_stats() in
multi_standalone_postgame_close()).  The host and clients must not save
stats to their local pilot files independently.  Multi_debrief_stats_
accept_code remaining -1 and triggering scoring_backout_accept() on the
host is correct behavior: kills are backed out of the local pilot file
because the tracker holds the authoritative record, not the local file.

The race condition fix (deferred send_debrief_event()) is sufficient to
prevent the crash: Pilot.update_stats() now creates kill entries with
correct m_okKills data, so scoring_backout_accept() can find and remove
them cleanly rather than hitting the UNREACHABLE.

Co-Authored-By: Claude Sonnet 4.6 <[email protected]>

* Move Multi_sexp_end_mission_pending from multiutil to multi and reset in multi_vars_init()

Addresses review feedback: the flag has no relation to multiutil, and
resetting it in multi_vars_init() ensures it is always cleared between
missions regardless of which code path was taken.

Co-Authored-By: Claude Opus 4.6 (1M context) <[email protected]>

---------

Co-authored-by: Claude Sonnet 4.6 <[email protected]>
2026-04-03 09:26:23 -04:00

1913 lines
54 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 "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;
bool Multi_sexp_end_mission_pending = false;
// -------------------------------------------------------------------------------------------------
// 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;
// sexp end-mission deferred debrief flag
Multi_sexp_end_mission_pending = false;
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(false);
// 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;
}
}
}
}