/* * 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 #include "globalincs/pstypes.h" #include "network/multimsgs.h" #include "network/multiutil.h" #include "network/multiui.h" #include "network/multi.h" #include "globalincs/linklist.h" #include "gamesequence/gamesequence.h" #include "hud/hudets.h" #include "hud/hudmessage.h" #include "hud/hudsquadmsg.h" #include "freespace.h" #include "io/timer.h" #include "mission/missiongoals.h" #include "mission/missionlog.h" #include "mission/missionmessage.h" #include "network/stand_gui.h" #include "gamesnd/gamesnd.h" #include "ship/shiphit.h" #include "render/3d.h" #include "playerman/player.h" #include "debris/debris.h" #include "missionui/missionweaponchoice.h" #include "missionui/missionshipchoice.h" #include "ship/shipfx.h" #include "popup/popup.h" #include "network/multi_ingame.h" #include "network/multiteamselect.h" #include "ai/aigoals.h" #include "ai/ai.h" #include "network/multi_campaign.h" #include "network/multi_team.h" #include "network/multi_respawn.h" #include "network/multi_observer.h" #include "network/multi_obj.h" #include "asteroid/asteroid.h" #include "network/multi_pmsg.h" #include "object/object.h" #include "object/objectshield.h" #include "stats/medals.h" #include "ship/ship.h" #include "weapon/weapon.h" #include "hud/hudreticle.h" #include "network/multi_pause.h" #include "network/multi_endgame.h" #include "missionui/missiondebrief.h" #include "network/multi_log.h" #include "weapon/emp.h" #include "weapon/muzzleflash.h" #include "network/multi_kick.h" #include "cmdline/cmdline.h" #include "weapon/flak.h" #include "weapon/beam.h" #include "network/multi_rate.h" #include "nebula/neblightning.h" #include "hud/hud.h" #include "missionui/missionscreencommon.h" #include "mission/missionbriefcommon.h" #include "network/multi_log.h" #include "object/objectdock.h" #include "cmeasure/cmeasure.h" #include "parse/sexp.h" #include "network/multi_fstracker.h" #include "network/multi_sw.h" #include "network/multi_sexp.h" #include "network/multi_mdns.h" #include "mission/missiongoals.h" #include "network/multi_interpolate.h" #include "network/multi_turret_manager.h" // #define _MULTI_SUPER_WACKY_COMPRESSION #ifdef _MULTI_SUPER_WACKY_COMPRESSION #define BITS 15 #define MAX_CODE ( ( 1 << BITS ) - 1 ) #define TABLE_SIZE 35023L #define END_OF_STREAM 256 #define BUMP_CODE 257 #define FLUSH_CODE 258 #define FIRST_CODE 259 #define UNUSED -1 typedef struct { int code_value; int parent_code; char character; } DICTIONARY; static DICTIONARY dict[TABLE_SIZE]; static char decode_stack[TABLE_SIZE]; static uint next_code; static int current_code_bits; static uint next_bump_code; typedef struct BitBuf { ubyte mask; int rack; ubyte *data; } BitBuf; void output_bits( BitBuf *bitbuf, uint code, int count ) { uint mask; mask = 1L << ( count - 1 ); while ( mask != 0) { if ( mask & code ) bitbuf->rack |= bitbuf->mask; bitbuf->mask >>= 1; if ( bitbuf->mask == 0 ) { *bitbuf->data++=(ubyte)bitbuf->rack; bitbuf->rack = 0; bitbuf->mask = 0x80; } mask >>= 1; } } uint input_bits( BitBuf *bitbuf, int bit_count ) { uint mask; uint return_value; mask = 1L << ( bit_count - 1 ); return_value = 0; while ( mask != 0) { if ( bitbuf->mask == 0x80 ) { bitbuf->rack = *bitbuf->data++; if ( bitbuf->rack == EOF ) return END_OF_STREAM; } if ( bitbuf->rack & bitbuf->mask ) return_value |= mask; mask >>= 1; bitbuf->mask >>= 1; if ( bitbuf->mask == 0 ) bitbuf->mask = 0x80; } return( return_value ); } static void InitializeDictionary() { uint i; for ( i = 0 ; i < TABLE_SIZE ; i++ ) dict[i].code_value = UNUSED; next_code = FIRST_CODE; current_code_bits = 9; next_bump_code = 511; } static uint find_child_node( int parent_code, int child_character ) { uint index; int offset; index = ( child_character << ( BITS - 8 ) ) ^ parent_code; if ( index == 0 ) offset = 1; else offset = TABLE_SIZE - index; for ( ; ; ) { if ( dict[ index ].code_value == UNUSED ) return( (uint) index ); if ( dict[ index ].parent_code == parent_code && dict[ index ].character == (char) child_character ) return( index ); if ( (int) index >= offset ) index -= offset; else index += TABLE_SIZE - offset; } } static uint decode_string( uint count, uint code ) { while ( code > 255 ) { decode_stack[ count++ ] = dict[ code ].character; code = dict[ code ].parent_code; } decode_stack[ count++ ] = (char) code; return( count ); } int lzw_compress( ubyte *outputbuf, ubyte *inputbuf, int input_size ) { BitBuf output; int character; int string_code; uint index; int i; // Init output bit buffer output.rack = 0; output.mask = 0x80; output.data = outputbuf; InitializeDictionary(); string_code = *inputbuf++; for ( i=1 ; i MAX_CODE ) { output_bits( &output, (unsigned long) FLUSH_CODE, current_code_bits ); InitializeDictionary(); } else if ( next_code > next_bump_code ) { output_bits( &output, (unsigned long) BUMP_CODE, current_code_bits ); current_code_bits++; next_bump_code <<= 1; next_bump_code |= 1; } } } output_bits( &output, (unsigned long) string_code, current_code_bits ); output_bits( &output, (unsigned long) END_OF_STREAM, current_code_bits); if ( output.mask != 0x80 ) *output.data++ = (ubyte)output.rack; return output.data-outputbuf; } int lzw_expand( ubyte *outputbuf, ubyte *inputbuf ) { BitBuf input; uint new_code; uint old_code; int character; uint count; uint counter; input.rack = 0; input.mask = 0x80; input.data = inputbuf; counter = 0; for ( ; ; ) { InitializeDictionary(); old_code = (uint) input_bits( &input, current_code_bits ); if ( old_code == END_OF_STREAM ) return counter; character = old_code; outputbuf[counter++] = ( ubyte )old_code; for ( ; ; ) { new_code = (uint) input_bits( &input, current_code_bits ); if ( new_code == END_OF_STREAM ) return counter; if ( new_code == FLUSH_CODE ) break; if ( new_code == BUMP_CODE ) { current_code_bits++; continue; } if ( new_code >= next_code ) { decode_stack[ 0 ] = (char) character; count = decode_string( 1, old_code ); } else { count = decode_string( 0, new_code ); } character = decode_stack[ count - 1 ]; while ( count > 0 ) outputbuf[counter++] = ( ubyte )decode_stack[ --count ]; dict[ next_code ].parent_code = old_code; dict[ next_code ].character = (char) character; next_code++; old_code = new_code; } } } #endif // process a join request packet add void add_join_request(ubyte *data, int *size, join_request *jr) { int packet_size = *size; ADD_STRING(jr->passwd); ADD_STRING(jr->callsign); ADD_STRING(jr->image_filename); ADD_STRING(jr->squad_filename); ADD_DATA(jr->player_rank); ADD_DATA(jr->flags); ADD_INT(jr->tracker_id); ADD_INT(jr->player_options.flags); ADD_INT(jr->player_options.obj_update_level); ADD_DATA(jr->version); ADD_DATA(jr->comp_version); ADD_STRING(jr->pxo_squad_name); *size = packet_size; } // process a join request packet get void get_join_request(ubyte *data, int *size, join_request *jr) { int offset = *size; GET_STRING(jr->passwd); GET_STRING(jr->callsign); GET_STRING(jr->image_filename); GET_STRING(jr->squad_filename); GET_DATA(jr->player_rank); GET_DATA(jr->flags); GET_INT(jr->tracker_id); GET_INT(jr->player_options.flags); GET_INT(jr->player_options.obj_update_level); GET_DATA(jr->version); GET_DATA(jr->comp_version); GET_STRING(jr->pxo_squad_name); *size = offset; } void add_net_addr(ubyte *data, int *size, net_addr *addr) { int packet_size = *size; ADD_DATA(addr->addr); ADD_USHORT(addr->port); *size = packet_size; } void get_net_addr(ubyte *data, int *size, net_addr *addr) { int offset = *size; GET_DATA(addr->addr); GET_USHORT(addr->port); *size = offset; } /* void add_vector_data(ubyte *data, int *size, vec3d vec) { int packet_size = *size; ADD_FLOAT(vec.xyz.x); ADD_FLOAT(vec.xyz.y); ADD_FLOAT(vec.xyz.z); *size = packet_size; } void get_vector_data(ubyte *data, int *size, vec3d vec) { int offset = *size; GET_FLOAT(vec.xyz.x); GET_FLOAT(vec.xyz.y); GET_FLOAT(vec.xyz.z); *size = offset; } */ // send the specified data packet to all players void multi_io_send(net_player *pl, const ubyte *data, int len) { // invalid if((pl == NULL) || (NET_PLAYER_NUM(pl) >= MAX_PLAYERS)){ return; } // don't do it for single player if(!(Game_mode & GM_MULTIPLAYER)){ return; } // sanity checks if(MULTIPLAYER_CLIENT){ // Assert(pl == Net_player); if(pl != Net_player){ return; } } else { // Assert(pl != Net_player); if(pl == Net_player){ return; } } // If this packet will push the buffer over MAX_PACKET_SIZE, send the current send_buffer if ((pl->s_info.unreliable_buffer_size + len) > MAX_PACKET_SIZE) { multi_io_send_force(pl); pl->s_info.unreliable_buffer_size = 0; } Assert((pl->s_info.unreliable_buffer_size + len) <= MAX_PACKET_SIZE); memcpy(pl->s_info.unreliable_buffer + pl->s_info.unreliable_buffer_size, data, len); pl->s_info.unreliable_buffer_size += len; } void multi_io_send_to_all(const ubyte *data, int length, const net_player *ignore) { int i; Assert(MULTIPLAYER_MASTER); // need to check for i > 1, hmmm... and connected. I don't know. for (i = 0; i < MAX_PLAYERS; i++ ) { if ( !MULTI_CONNECTED(Net_players[i]) || (Net_player == &Net_players[i])){ continue; } // maybe ignore a player if((ignore != NULL) && (&Net_players[i] == ignore)){ continue; } // ingame joiners not waiting to select a ship doesn't get any packets if ( (Net_players[i].flags & NETINFO_FLAG_INGAME_JOIN) && !(Net_players[i].flags & INGAME_JOIN_FLAG_PICK_SHIP) ){ continue; } // send it multi_io_send(&Net_players[i], data, length); } } void multi_io_send_force(net_player *pl) { // invalid if((pl == NULL) || (NET_PLAYER_NUM(pl) >= MAX_PLAYERS)){ return; } // don't do it for single player if(!(Game_mode & GM_MULTIPLAYER)){ return; } // send everything in if (MULTIPLAYER_MASTER) { psnet_send(&pl->p_info.addr, pl->s_info.unreliable_buffer, pl->s_info.unreliable_buffer_size, NET_PLAYER_NUM(pl)); // add the bytes sent to this player pl->sv_bytes_sent += pl->s_info.unreliable_buffer_size; } else { psnet_send(&Netgame.server_addr, pl->s_info.unreliable_buffer, pl->s_info.unreliable_buffer_size, NET_PLAYER_NUM(pl)); } pl->s_info.unreliable_buffer_size = 0; } // send the data packet to all players via their reliable sockets void multi_io_send_reliable(net_player *pl, const ubyte *data, int len) { // invalid if((pl == NULL) || (NET_PLAYER_NUM(pl) >= MAX_PLAYERS)){ return; } // don't do it for single player if(!(Game_mode & GM_MULTIPLAYER)){ return; } // sanity checks if(MULTIPLAYER_CLIENT){ // Assert(pl == Net_player); if(pl != Net_player){ return; } } else { // Assert(pl != Net_player); if(pl == Net_player){ return; } } // If this packet will push the buffer over MAX_PACKET_SIZE, send the current send_buffer if ((pl->s_info.reliable_buffer_size + len) > MAX_PACKET_SIZE) { multi_io_send_reliable_force(pl); pl->s_info.reliable_buffer_size = 0; } Assert((pl->s_info.reliable_buffer_size + len) <= MAX_PACKET_SIZE); memcpy(pl->s_info.reliable_buffer + pl->s_info.reliable_buffer_size, data, len); pl->s_info.reliable_buffer_size += len; } void multi_io_send_to_all_reliable(const ubyte* data, int length, const net_player *ignore) { int i; Assert(MULTIPLAYER_MASTER); // need to check for i > 1, hmmm... and connected. I don't know. for (i = 0; i < MAX_PLAYERS; i++ ) { if ( !MULTI_CONNECTED(Net_players[i]) || (Net_player == &Net_players[i])){ continue; } // maybe ignore a player if((ignore != NULL) && (&Net_players[i] == ignore)){ continue; } // ingame joiners not waiting to select a ship doesn't get any packets if ( (Net_players[i].flags & NETINFO_FLAG_INGAME_JOIN) && !(Net_players[i].flags & INGAME_JOIN_FLAG_PICK_SHIP) ){ continue; } // send it multi_io_send_reliable(&Net_players[i], data, length); } } void multi_io_send_reliable_force(net_player *pl) { // invalid if((pl == NULL) || (NET_PLAYER_NUM(pl) >= MAX_PLAYERS)){ return; } // don't do it for single player if(!(Game_mode & GM_MULTIPLAYER)){ return; } // send everything in if(MULTIPLAYER_MASTER) { psnet_rel_send(pl->reliable_socket, pl->s_info.reliable_buffer, pl->s_info.reliable_buffer_size, NET_PLAYER_NUM(pl)); } else if(Net_player != NULL){ psnet_rel_send(Net_player->reliable_socket, pl->s_info.reliable_buffer, pl->s_info.reliable_buffer_size, NET_PLAYER_NUM(pl)); } pl->s_info.reliable_buffer_size = 0; } // send all buffered packets void multi_io_send_buffered_packets() { int idx; // don't do it for single player if(!(Game_mode & GM_MULTIPLAYER)){ return; } // server if(MULTIPLAYER_MASTER){ for(idx=0; idx 0){ multi_io_send_force(&Net_players[idx]); Net_players[idx].s_info.unreliable_buffer_size = 0; } // force reliable data if(Net_players[idx].s_info.reliable_buffer_size > 0){ multi_io_send_reliable_force(&Net_players[idx]); Net_players[idx].s_info.reliable_buffer_size = 0; } } } } // clients else if(Net_player != NULL){ // force unreliable data if(Net_player->s_info.unreliable_buffer_size > 0){ multi_io_send_force(Net_player); Net_player->s_info.unreliable_buffer_size = 0; } // force reliable data if(Net_player->s_info.reliable_buffer_size > 0){ multi_io_send_reliable_force(Net_player); Net_player->s_info.reliable_buffer_size = 0; } } } //********************************************************************************************************* // Game Chat Packet //********************************************************************************************************* /* struct fs2_game_chat_packet { char packet_signature; //0xC3 short from_player_id; int server_msg; char mode; // variable record if (mode) short to_player_id; else { int i; char expr[i]; } int j; char message[j]; } */ //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // send a general game chat packet (if msg_mode == MULTI_MSG_TARGET, need to pass in "to", if == MULTI_MSG_EXPR, need to pass in expr) void send_game_chat_packet(net_player *from, const char *msg, int msg_mode, net_player *to, const char *expr, ubyte server_msg) { ubyte data[MAX_PACKET_SIZE],mode; int packet_size,idx; bool undeliverable = true; BUILD_HEADER(GAME_CHAT); // add the id ADD_SHORT(from->player_id); // add the message mode and if in MSG_TARGET mode, add who the target is ADD_DATA(server_msg); mode = (ubyte)msg_mode; ADD_DATA(mode); switch(mode){ case MULTI_MSG_TARGET: Assert(to != NULL); ADD_SHORT(to->player_id); break; case MULTI_MSG_EXPR: Assert(expr != NULL); ADD_STRING(expr); break; } // add the message itself ADD_STRING( msg ); if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ switch(mode){ // message all players case MULTI_MSG_ALL: for(idx=0;idxp_info.team)){ multi_io_send_reliable(&Net_players[idx], data, packet_size); } } break; // message only hostile players case MULTI_MSG_HOSTILE: for(idx=0;idxp_info.team)){ multi_io_send_reliable(&Net_players[idx], data, packet_size); } } break; // message the player's target case MULTI_MSG_TARGET: Assert(to != NULL); if(MULTI_CONNECTED((*to)) && !MULTI_STANDALONE((*to))){ multi_io_send_reliable(to, data, packet_size); } break; // message all players who match the expression string case MULTI_MSG_EXPR: Assert(expr != NULL); for(idx=0;idxflags & NETINFO_FLAG_AM_MASTER){ // if he's targeting a specific player, find out who it is if(mode == MULTI_MSG_TARGET){ to_player_index = find_player_index(to); } else { to_player_index = -1; } // if we couldn't find who sent the message or who should be getting the message, the bail if(((to_player_index == -1) && (mode == MULTI_MSG_TARGET)) || (player_index == -1)){ return; } // determine if _I_ should be seeing the text if(Game_mode & GM_STANDALONE_SERVER){ should_display = 1; } // check against myself for several specific cases else { if((mode == MULTI_MSG_ALL) || ((mode == MULTI_MSG_FRIENDLY) && (Net_player->p_info.team == Net_players[player_index].p_info.team)) || ((mode == MULTI_MSG_HOSTILE) && (Net_player->p_info.team != Net_players[player_index].p_info.team)) || ((mode == MULTI_MSG_TARGET) && (MY_NET_PLAYER_NUM == to_player_index)) || ((mode == MULTI_MSG_EXPR) && multi_msg_matches_expr(Net_player,expr)) ){ should_display = 1; } } // if we're the server of a game, we need to rebroadcast to all other players switch(mode){ // individual target mission case MULTI_MSG_TARGET: // if I was the inteneded target, or we couldn't find the intended target, don't rebroadcast if(to_player_index != MY_NET_PLAYER_NUM){ send_game_chat_packet(&Net_players[player_index], msg, (int)mode, &Net_players[to_player_index], NULL, server_msg); } break; // expression mode case MULTI_MSG_EXPR: send_game_chat_packet(&Net_players[player_index], msg, (int)mode, NULL, expr, server_msg); break; // all other modes default : send_game_chat_packet(&Net_players[player_index], msg, (int)mode, NULL, NULL, server_msg); break; } } // if a client receives this packet, its always ok for him to display it else { should_display = 1; } // if we're not on a standalone if(should_display){ if(server_msg == 2){ HUD_printf("%s", msg); } else { multi_display_chat_msg(msg, player_index, !server_msg); } } } //********************************************************************************************************* // Hud Message packet //********************************************************************************************************* /* struct fs2_game_chat_packet { char packet_signature; //0xC1 HUD_MSG int msg_size; char msg[msg_size]; } */ //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // broadcast a hud message to all players void send_hud_msg_to_all( char* msg ) { ubyte data[MAX_PACKET_SIZE]; int packet_size; // only the server should be sending this packet BUILD_HEADER(HUD_MSG); ADD_STRING(msg); multi_io_send_to_all( data, packet_size ); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // process an incoming hud message packet void process_hud_message(ubyte* data, header* hinfo) { int offset; char msg_buffer[255]; offset = HEADER_LENGTH; GET_STRING(msg_buffer); PACKET_SET_SIZE(); // this is the only safe place to do this since only in the mission is the HUD guaranteed to be inited if(Game_mode & GM_IN_MISSION){ HUD_printf("%s", msg_buffer); } } //********************************************************************************************************* // Join Packet //********************************************************************************************************* /* struct fs2_game_chat_packet { char packet_signature; //0xC1 HUD_MSG int msg_size; char msg[msg_size]; } */ //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // send a join packet request to the specified address (should be a server) void send_join_packet(net_addr* addr,join_request *jr) { ubyte data[MAX_PACKET_SIZE]; int packet_size; // build the header and add the request BUILD_HEADER(JOIN); add_join_request(data, &packet_size, jr); psnet_send(addr, data, packet_size); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // process an incoming join request packet void process_join_packet(ubyte* data, header* hinfo) { join_request jr; int offset; int ret_code; int host_restr_mode; int team0_avail,team1_avail; char join_string[255]; net_addr addr; // only the server of the game should ever receive this packet if ( !(Net_player->flags & NETINFO_FLAG_AM_MASTER) ) return; offset = HEADER_LENGTH; // read in the request info memset(&jr,0,sizeof(join_request)); get_join_request(data, &offset, &jr); PACKET_SET_SIZE(); // fill in the address information of where this came from fill_net_addr(&addr, hinfo->addr, hinfo->port); // determine if we should accept this guy, or return a reason we should reject him // see the DENY_* codes in multi.h ret_code = multi_eval_join_request(&jr,&addr); // evaluate the return code switch(ret_code) { // he should be accepted case -1 : break; // we have to query the host because this is a restricted game case JOIN_QUERY_RESTRICTED : if(!(Game_mode & GM_STANDALONE_SERVER)){ // notify the host of the event snd_play(gamesnd_get_game_sound(GameSounds::CUE_VOICE)); } // set the query timestamp Multi_restr_query_timestamp = ui_timestamp(MULTI_QUERY_RESTR_STAMP); Netgame.flags |= NG_FLAG_INGAME_JOINING; // determine what mode we're in host_restr_mode = -1; memset(join_string,0,255); if((Netgame.type_flags & NG_TYPE_TEAM) && Netgame.mode == NG_MODE_RESTRICTED){ multi_player_ships_available(&team0_avail,&team1_avail); if(team0_avail && team1_avail){ host_restr_mode = MULTI_JOIN_RESTR_MODE_4; sprintf(join_string,"Player %s has tried to join. Accept on team 1 or 2 ?",jr.callsign); } else if(team0_avail && !team1_avail){ host_restr_mode = MULTI_JOIN_RESTR_MODE_2; sprintf(join_string,"Player %s has tried to join team 0, accept y/n ? ?",jr.callsign); } else if(!team0_avail && team1_avail){ host_restr_mode = MULTI_JOIN_RESTR_MODE_3; sprintf(join_string,"Player %s has tried to join team 1, accept y/n ?",jr.callsign); } } else if(Netgame.mode == NG_MODE_RESTRICTED){ host_restr_mode = MULTI_JOIN_RESTR_MODE_1; sprintf(join_string,XSTR("Player %s has tried to join, accept y/n ?",715),jr.callsign); } Assert(host_restr_mode != -1); // store the request info memcpy(&Multi_restr_join_request,&jr,sizeof(join_request)); memcpy(&Multi_restr_addr,&addr,sizeof(net_addr)); Multi_join_restr_mode = host_restr_mode; // if i'm the standalone server, I need to send a query to the host if(Game_mode & GM_STANDALONE_SERVER){ send_host_restr_packet(jr.callsign,0,Multi_join_restr_mode); } else { HUD_printf("%s", join_string); } // NETLOG ml_printf(NOX("Receive restricted join request from %s"), jr.callsign); return; // he'e being denied for some reason default : // send him the reason he is being denied send_deny_packet(&addr,ret_code); Netgame.flags &= ~(NG_FLAG_INGAME_JOINING); return; } // process the rest of the request multi_process_valid_join_request(&jr,&addr); } //********************************************************************************************************* // New Player Packet //********************************************************************************************************* /* struct fs2_new_player_packet { char packet_signature; // 0xB4 NOTIFY_NEW_PLAYER int new_player_num; net_addr player_addr; short player_id; int flags; int i; char callsign[i]; int j; char plyr_image_filename[j]; int k; char plyr_squad_filename[k]; int l; char plyr_pxo_squad_name[l]; } */ //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // send a notification that a new player has joined the game (if target != NULL, broadcast the packet) void send_new_player_packet(int new_player_num,net_player *target) { ubyte data[MAX_PACKET_SIZE], val; int packet_size = 0; BUILD_HEADER( NOTIFY_NEW_PLAYER ); // add the new player's info val = static_cast(new_player_num); ADD_DATA(val); // ADD_DATA(Net_players[new_player_num].p_info.addr); add_net_addr(data, &packet_size, &Net_players[new_player_num].p_info.addr); ADD_SHORT(Net_players[new_player_num].player_id); ADD_INT(Net_players[new_player_num].flags); ADD_STRING(Net_players[new_player_num].m_player->callsign); ADD_STRING(Net_players[new_player_num].m_player->image_filename); ADD_STRING(Net_players[new_player_num].m_player->m_squad_filename); ADD_STRING(Net_players[new_player_num].p_info.pxo_squad_name); val = (ubyte)Net_players[new_player_num].p_info.team; ADD_DATA(val); // broadcast the data if(target != NULL){ multi_io_send_reliable(target, data, packet_size); } else { multi_io_send_to_all_reliable(data, packet_size); } } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // process a notification for a new player who has joined the game void process_new_player_packet(ubyte* data, header* hinfo) { int already_in_game = 0; int offset, player_num, new_flags; net_addr new_addr; char new_player_name[CALLSIGN_LEN+2] = ""; char new_player_image[MAX_FILENAME_LEN+1] = ""; char new_player_squad[MAX_FILENAME_LEN+1] = ""; char new_player_pxo_squad[LOGIN_LEN+1] = ""; char notify_string[256]; ubyte team, new_player_num; short new_id; offset = HEADER_LENGTH; // get the new players information GET_DATA(new_player_num); // GET_DATA(new_addr); get_net_addr(data, &offset, &new_addr); GET_SHORT(new_id); GET_INT(new_flags); GET_STRING(new_player_name); GET_STRING(new_player_image); GET_STRING(new_player_squad); GET_STRING(new_player_pxo_squad); GET_DATA(team); PACKET_SET_SIZE(); player_num = multi_find_open_player_slot(); Assert(player_num != -1); // note that this new code does not check for duplicate IPs. It merely checks to see if // the slot referenced by new_player_num is already occupied by a connected player if(MULTI_CONNECTED(Net_players[new_player_num])){ already_in_game=1; } // if he's not alreayd in the game for one reason or another if ( !already_in_game ) { if ( Game_mode & GM_IN_MISSION ){ HUD_sourced_printf(HUD_SOURCE_COMPUTER, XSTR("%s has entered the game\n",716), new_player_name); } // create the player if(new_flags & NETINFO_FLAG_OBSERVER){ multi_obs_create_player(new_player_num,new_player_name,&new_addr,&Players[player_num]); Net_players[new_player_num].flags |= new_flags; } else { multi_create_player( new_player_num, &Players[player_num],new_player_name, &new_addr, -1, new_id ); Net_players[new_player_num].flags |= new_flags; } // copy in the filename if(new_player_image[0] != '\0'){ strcpy_s(Net_players[new_player_num].m_player->image_filename, new_player_image); } else { strcpy_s(Net_players[new_player_num].m_player->image_filename, ""); } // copy his pilot squad filename Net_players[new_player_num].m_player->insignia_texture = -1; player_set_squad_bitmap(Net_players[new_player_num].m_player, new_player_squad, true); // copy in his pxo squad name strcpy_s(Net_players[new_player_num].p_info.pxo_squad_name, new_player_pxo_squad); // since we just created the player, set the last_heard_time here. Net_players[new_player_num].last_heard_time = timer_get_fixed_seconds(); Net_players[new_player_num].p_info.team = team; Net_players[new_player_num].player_id = new_id; // zero out this players ping multi_ping_reset(&Net_players[new_player_num].s_info.ping); // add a chat message if(*Net_players[new_player_num].m_player->callsign){ sprintf(notify_string,XSTR("<%s has joined>",717),Net_players[new_player_num].m_player->callsign); multi_display_chat_msg(notify_string,0,0); } } // NETLOG ml_printf(NOX("Received notification of new player %s"), Net_players[new_player_num].m_player->callsign); // let the current ui screen know someone joined switch(gameseq_get_state()){ case GS_STATE_MULTI_HOST_SETUP : multi_create_handle_join(&Net_players[new_player_num]); break; case GS_STATE_MULTI_CLIENT_SETUP : multi_jw_handle_join(&Net_players[new_player_num]); break; } } //********************************************************************************************************* // Accept Player Data Packet //********************************************************************************************************* /* struct fs2_accept_player_data { char packet_signature; //ACCEPT_PLAYER_DATA ubyte stop; int player_num; net_addr plyr_addr; int player_id; int i; char callsign[i]; int j; char plr_image_filename[j]; int k; char plr_squad_filename[k] int l char plr_pxo_squadname[l]; int flags; if (is_ingame) int net_signature; }; */ //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ #define PLAYER_DATA_SLOP 100 void send_accept_player_data( net_player *npp, int is_ingame ) { int packet_size; uint8_t i; ubyte data[MAX_PACKET_SIZE], stop; BUILD_HEADER(ACCEPT_PLAYER_DATA); // add in the netplayer data for all players stop = APD_NEXT; for (i=0; iplayer_id == Net_players[i].player_id ){ continue; } // add the stop byte ADD_DATA(stop); // add the player's number ADD_DATA(i); // add the player's address // ADD_DATA(Net_players[i].p_info.addr); add_net_addr(data, &packet_size, &Net_players[i].p_info.addr); // add his id# ADD_SHORT(Net_players[i].player_id); // add his callsign ADD_STRING(Net_players[i].m_player->callsign); // add his image filename ADD_STRING(Net_players[i].m_player->image_filename); // add his squad filename ADD_STRING(Net_players[i].m_player->m_squad_filename); // add his PXO squad name ADD_STRING(Net_players[i].p_info.pxo_squad_name); // add his flags ADD_INT(Net_players[i].flags); // add his object's net sig if ( is_ingame ) { ADD_USHORT( Objects[Net_players[i].m_player->objnum].net_signature ); } if ( (packet_size + PLAYER_DATA_SLOP) > MAX_PACKET_SIZE ) { stop = APD_END_PACKET; ADD_DATA(stop); multi_io_send_reliable( npp, data, packet_size ); BUILD_HEADER(ACCEPT_PLAYER_DATA); stop = APD_NEXT; } } // add the stop byte stop = APD_END_DATA; ADD_DATA(stop); multi_io_send_reliable(npp, data, packet_size); } //+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+ // process the player data from the server void process_accept_player_data( ubyte *data, header *hinfo ) { int offset, player_slot_num, new_flags; char name[CALLSIGN_LEN + 1] = ""; char image_name[MAX_FILENAME_LEN + 1] = ""; char squad_name[MAX_FILENAME_LEN + 1] = ""; char pxo_squad_name[LOGIN_LEN+1] = ""; short player_id; net_addr addr; ubyte stop; ushort ig_signature; uint8_t player_num; offset = HEADER_LENGTH; GET_DATA(stop); while ( stop == APD_NEXT ) { player_slot_num = multi_find_open_player_slot(); Assert(player_slot_num != -1); // get the player's number GET_DATA(player_num); // add the player's address // GET_DATA(addr); get_net_addr(data, &offset, &addr); // get the player's id# GET_SHORT(player_id); // get his callsign GET_STRING(name); // add his image filename GET_STRING(image_name); // get his squad logo filename GET_STRING(squad_name); // get his PXO squad name GET_STRING(pxo_squad_name); // get his flags GET_INT(new_flags); if (Net_players[player_num].flags & NETINFO_FLAG_OBSERVER) { multi_obs_create_player(player_num, name, &addr, &Players[player_slot_num]); } else { multi_create_player(player_num, &Players[player_slot_num], name, &addr, -1, player_id); } // copy his image filename strcpy_s(Net_players[player_num].m_player->image_filename, image_name); // copy his pilot squad filename Net_players[player_num].m_player->insignia_texture = -1; player_set_squad_bitmap(Net_players[player_num].m_player, squad_name, true); // copy his pxo squad name strcpy_s(Net_players[player_num].p_info.pxo_squad_name, pxo_squad_name); // set his player id# Net_players[player_num].player_id = player_id; // mark him as being connected Net_players[player_num].flags |= NETINFO_FLAG_CONNECTED; Net_players[player_num].flags |= new_flags; // set the server pointer if ( Net_players[player_num].flags & NETINFO_FLAG_AM_MASTER ) { Netgame.server = &Net_players[player_num]; Netgame.server->last_heard_time = timer_get_fixed_seconds(); // also - always set the server address to be where this data came from, NOT from // the data in the packet fill_net_addr(&Net_players[player_num].p_info.addr, hinfo->addr, hinfo->port); } // set the host pointer if ( Net_players[player_num].flags & NETINFO_FLAG_GAME_HOST ) { Netgame.host = &Net_players[player_num]; } // read in the player's object net signature and store as his objnum for now if ( Net_player->flags & NETINFO_FLAG_ACCEPT_INGAME ) { GET_USHORT( ig_signature ); Net_players[player_num].m_player->objnum = ig_signature; } // get the stop byte GET_DATA(stop); } PACKET_SET_SIZE(); if ( stop == APD_END_DATA ) { // if joining a game automatically, set the connect address to NULl so we don't try and // do this next time we enter a game if (Cmdline_connect_addr != NULL) { Cmdline_connect_addr = NULL; } // send my stats to the server if I'm not in observer mode if (!(Net_player->flags & NETINFO_FLAG_ACCEPT_OBSERVER)) { send_player_stats_block_packet(Net_player, STATS_ALLTIME); } // if i'm being accepted as a host, then move into the host setup state if ( Net_player->flags & NETINFO_FLAG_ACCEPT_HOST) { // set my permission bits Net_player->flags |= NETINFO_FLAG_GAME_HOST; Net_player->state = NETPLAYER_STATE_STD_HOST_SETUP; gameseq_post_event(GS_EVENT_MULTI_START_GAME); } if ( Net_player->flags & NETINFO_FLAG_ACCEPT_OBSERVER) { Net_player->flags |= NETINFO_FLAG_OBSERVER; // since observers can join 1 of 2 ways, only do this if we're not doing an ingame observer join if ( !(Net_player->flags & NETINFO_FLAG_ACCEPT_INGAME) ) { gameseq_post_event(GS_EVENT_MULTI_CLIENT_SETUP); } } if ( Net_player->flags & NETINFO_FLAG_ACCEPT_CLIENT) { gameseq_post_event(GS_EVENT_MULTI_CLIENT_SETUP); } if ( Net_player->flags & NETINFO_FLAG_ACCEPT_INGAME) { // flag myself as being an ingame joiner Net_player->flags |= NETINFO_FLAG_INGAME_JOIN; // move myself into the ingame join mission sync state Multi_sync_mode = MULTI_SYNC_INGAME; gameseq_post_event(GS_EVENT_MULTI_MISSION_SYNC); } // update my options on the server multi_options_update_local(); // if we're in PXO mode, mark it down in our player struct if(MULTI_IS_TRACKER_GAME){ Player->flags |= PLAYER_FLAGS_HAS_PLAYED_PXO; Player->save_flags |= PLAYER_FLAGS_HAS_PLAYED_PXO; } } } //********************************************************************************************************* // Accept Player Packet //********************************************************************************************************* /* struct fs2_accept_packet { char packet_signature; int code; if (code & ACCEPT_INGAME) { int gm_mis_fname_len; char mission_filename[gm_mis_fname_len]; unsigned char ingame_joining_team; if (ingame_joining_team == 1) unsigned char ingame_join_team; } int skill_level; int player_num; short player_id; int netgame_type_flags; } */ // send an accept packet to a client in response to a request to join the game void send_accept_packet(int new_player_num, int code, int ingame_join_team) { int packet_size, i; ubyte data[MAX_PACKET_SIZE],val; char notify_string[256]; // sanity Assert(new_player_num >= 0); // setup his "reliable" socket Net_players[new_player_num].last_heard_time = timer_get_fixed_seconds(); // build the packet header packet_size = 0; BUILD_HEADER(ACCEPT); // add the accept code ADD_INT(code); // add code specific accept data if (code & ACCEPT_INGAME) { // the game filename ADD_STRING(Game_current_mission_filename); // if he is joining on a specific team, mark it here if(ingame_join_team != -1){ val = 1; ADD_DATA(val); val = (ubyte)ingame_join_team; ADD_DATA(val); } else { val = 0; ADD_DATA(val); } } if (code & ACCEPT_OBSERVER) { Assert(!(code & (ACCEPT_CLIENT | ACCEPT_HOST))); } if (code & ACCEPT_HOST) { Assert(!(code & (ACCEPT_CLIENT | ACCEPT_OBSERVER | ACCEPT_INGAME))); } if (code & ACCEPT_CLIENT) { Assert(!(code & (ACCEPT_HOST | ACCEPT_OBSERVER | ACCEPT_INGAME))); } // add the current skill level setting on the host // sanity check - reset skill level to default before sending if out of range if (Game_skill_level < 0 || Game_skill_level >= NUM_SKILL_LEVELS) { Warning(LOCATION, "Trying to send packet containing invalid skill level %i! Valid range 0 to %i. Resetting to default.", Game_skill_level, NUM_SKILL_LEVELS); Game_skill_level = game_get_default_skill_level(); } ADD_INT(Game_skill_level); // add this guys player num val = static_cast(new_player_num); ADD_DATA(val); // add his player id ADD_SHORT(Net_players[new_player_num].player_id); // add netgame type flags ADD_INT(Netgame.type_flags); // actually send the packet psnet_send(&Net_players[new_player_num].p_info.addr, data, packet_size); // if he's not an observer, inform all the other players in the game about him // inform the other players in the game about this new player for (i=0; icallsign){ sprintf(notify_string,XSTR("<%s has joined>",717), Net_players[new_player_num].m_player->callsign); multi_display_chat_msg(notify_string, 0, 0); } // handle any team vs. team details if (!(code & ACCEPT_OBSERVER)) { multi_team_handle_join(&Net_players[new_player_num]); } // NETLOG if(Net_players[new_player_num].tracker_player_id >= 0){ ml_printf(NOX("Server accepted %s (tracker id %d) as new client"), Net_players[new_player_num].m_player->callsign, Net_players[new_player_num].tracker_player_id); } else { ml_printf(NOX("Server accepted %s as new client"), Net_players[new_player_num].m_player->callsign); } } // process an accept packet from the server extern int Select_default_ship; void process_accept_packet(ubyte* data, header* hinfo) { int code, offset; ubyte val, team = 0, my_player_num; short player_id; // get the accept code offset = HEADER_LENGTH; GET_INT(code); // read in the accept code specific data val = 0; if (code & ACCEPT_INGAME) { // the game filename GET_STRING(Game_current_mission_filename); mprintf(("Got mission filename %s\n", Game_current_mission_filename)); Select_default_ship = 0; // determine if I'm being placed on a team GET_DATA(val); if(val){ GET_DATA(team); } } if (code & ACCEPT_OBSERVER) { Assert(!(code & (ACCEPT_CLIENT | ACCEPT_HOST))); } if (code & ACCEPT_HOST) { Assert(!(code & (ACCEPT_CLIENT | ACCEPT_OBSERVER | ACCEPT_INGAME))); } if (code & ACCEPT_CLIENT) { Assert(!(code & (ACCEPT_HOST | ACCEPT_OBSERVER | ACCEPT_INGAME))); } // fill in the netgame server address fill_net_addr( &Netgame.server_addr, hinfo->addr, hinfo->port ); // get the skill level setting GET_INT(Game_skill_level); if (Game_skill_level < 0 || Game_skill_level >= NUM_SKILL_LEVELS) { Warning(LOCATION, "Received packet containing invalid skill level %i! Valid range 0 to %i. Resetting to default.", Game_skill_level, NUM_SKILL_LEVELS); Game_skill_level = game_get_default_skill_level(); } // get my netplayer number GET_DATA(my_player_num); // get my id # GET_SHORT(player_id); // get netgame type flags GET_INT(Netgame.type_flags); // setup the Net_players structure for myself first Net_player = &Net_players[my_player_num]; Net_player->flags = 0; Net_player->tracker_player_id = Multi_tracker_id; Net_player->player_id = player_id; Net_player->s_info.xfer_handle = -1; // stuff_netplayer_info( Net_player, &Psnet_my_addr, Ships[Objects[Player->objnum].instance].ship_info_index, Player ); stuff_netplayer_info( Net_player, &Psnet_my_addr, 0, Player ); multi_options_local_load(&Net_player->p_info.options, Net_player); Net_player->p_info.team = team; // set accept code in netplayer for this guy if ( code & ACCEPT_INGAME ){ Net_player->flags |= NETINFO_FLAG_ACCEPT_INGAME; } if ( code & ACCEPT_OBSERVER ){ Net_player->flags |= NETINFO_FLAG_ACCEPT_OBSERVER; } if ( code & ACCEPT_HOST ){ Net_player->flags |= NETINFO_FLAG_ACCEPT_HOST; } if ( code & ACCEPT_CLIENT ){ Net_player->flags |= NETINFO_FLAG_ACCEPT_CLIENT; } // if I have hacked data if(game_hacked_data()){ Net_player->flags |= NETINFO_FLAG_HAXOR; } // if we're supposed to flush our local data cache, do so now if(Net_player->p_info.options.flags & MLO_FLAG_FLUSH_CACHE){ multi_flush_multidata_cache(); } Net_player->sv_bytes_sent = 0; Net_player->sv_last_pl = -1; Net_player->cl_bytes_recvd = 0; Net_player->cl_last_pl = -1; // intiialize endgame stuff multi_endgame_init(); PACKET_SET_SIZE(); // make a call to psnet to initialize and try to connect with the server. psnet_rel_connect_to_server( &Net_player->reliable_socket, &Netgame.server_addr ); if ( Net_player->reliable_socket == PSNET_INVALID_SOCKET ) { multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_CONNECT_FAIL); } } //********************************************************************************************************* // Player Leave Packet //********************************************************************************************************* /* struct fs2_leave_game_packet { char packet_signature; char kicked_reason; short player_id; } */ // send a notice that the player at net_addr is leaving (if target is NULL, the broadcast the packet) void send_leave_game_packet(short player_id, int kicked_reason, net_player *target) { ubyte data[MAX_PACKET_SIZE]; char val; int packet_size = 0; BUILD_HEADER(LEAVE_GAME); // add a flag indicating whether he was kicked or not val = (char)kicked_reason; ADD_DATA(val); if (player_id < 0) { ADD_SHORT(Net_player->player_id); // inform the host that we are leaving the game if (Net_player->flags & NETINFO_FLAG_AM_MASTER) { multi_io_send_to_all_reliable(data, packet_size); } else { multi_io_send_reliable(Net_player, data, packet_size); } } // this is the case where to server is tossing a player (or indicating a respawned player has quit or become an observer) // so he has to tell everyone that this guy left else { nprintf(("Network","Sending a leave game packet to all players (server)\n")); // a couple of important checks Assert(player_id != Net_player->player_id); Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER); // add the id of the guy to be kicked ADD_SHORT(player_id); // broadcast to everyone if (target == NULL) { multi_io_send_to_all_reliable(data, packet_size); } else { multi_io_send_reliable(target, data, packet_size); } } } // process a notification that a player has left the game void process_leave_game_packet(ubyte* data, header* hinfo) { int offset; short deader_id; int player_num; char kicked_reason; char str[512]; offset = HEADER_LENGTH; // get whether he was kicked GET_DATA(kicked_reason); // get the address of the guy who is to leave GET_SHORT(deader_id); PACKET_SET_SIZE(); // determine who is dropping and printf out a notification player_num = find_player_index(deader_id); if (player_num == -1) { nprintf(("Network", "Received leave game packet for unknown player, ignoring\n")); return; } else { nprintf(("Network", "Received a leave game notice for %s\n", Net_players[player_num].m_player->callsign)); } // a hook to display that a player was kicked if (kicked_reason >= 0){ // if it was me that was kicked, leave the game if((Net_player != NULL) && (Net_player->player_id == deader_id)){ int notify_code; switch(kicked_reason){ case KICK_REASON_BAD_XFER: notify_code = MULTI_END_NOTIFY_KICKED_BAD_XFER; break; case KICK_REASON_CANT_XFER: notify_code = MULTI_END_NOTIFY_KICKED_CANT_XFER; break; case KICK_REASON_INGAME_ENDED: notify_code = MULTI_END_NOTIFY_KICKED_INGAME_ENDED; break; default: notify_code = MULTI_END_NOTIFY_KICKED; break; } multi_quit_game(PROMPT_NONE, notify_code); return; // otherwise indicate someone was kicked } else { nprintf(("Network","%s was kicked\n",Net_players[player_num].m_player->callsign)); // display the result memset(str, 0, 512); multi_kick_get_text(&Net_players[player_num], kicked_reason, str); multi_display_chat_msg(str, player_num, 0); } } // first of all, if we're the master, we should be rebroadcasting this packet if (Net_player->flags & NETINFO_FLAG_AM_MASTER) { char msg[255]; sprintf(msg, XSTR("%s has left the game",719), Net_players[player_num].m_player->callsign ); if (!(Game_mode & GM_STANDALONE_SERVER)){ HUD_sourced_printf(HUD_SOURCE_HIDDEN, "%s", msg); } send_hud_msg_to_all(msg); multi_io_send_to_all_reliable(data, offset); } // leave the game if the host and/or master has dropped /* if (((Net_players[player_num].flags & NETINFO_FLAG_AM_MASTER) || (Net_players[player_num].flags & NETINFO_FLAG_GAME_HOST)) ) { nprintf(("Network","Host and/or server has left the game - aborting...\n")); // NETLOG ml_string(NOX("Host and/or server has left the game")); // if the host leaves in the debriefing state, we should still wait until the player selects accept before we quit if (gameseq_get_state() != GS_STATE_DEBRIEF) { multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_SERVER_LEFT); } delete_player(player_num); } else { */ delete_player(player_num); // OSAPI GUI stuff (if standalone) if (Game_mode & GM_STANDALONE_SERVER) { // returns true if we should reset the standalone if (std_remove_player(&Net_players[player_num])) { nprintf(("Network", "Should reset!!\n")); return; } // update these gui vals std_connect_set_host_connect_status(); std_connect_set_connect_count(); } } //********************************************************************************************************* // Game active packet //********************************************************************************************************* /* struct fs2_game_active_packet { char packet_signature; ubyte server_version; ubyte compat_version; int len1; char netgame_name[len1]; int len2; char netgame_mission_name[len2]; int len3; char netgame_title[len3]; ubyte num_players; unsigned short flags; } */ // send information about this currently active game to the specified address void send_game_active_packet(net_addr* addr) { int packet_size; ushort flags; ubyte data[MAX_PACKET_SIZE],val; // build the header and add the data BUILD_HEADER(GAME_ACTIVE); // add the server version and compatible version # val = MULTI_FS_SERVER_VERSION; ADD_DATA(val); val = MULTI_FS_SERVER_COMPATIBLE_VERSION; ADD_DATA(val); // use 4-byte version of *_STRING to remain compatible with older builds ADD_STRING_32(Netgame.name); ADD_STRING_32(Netgame.mission_name); ADD_STRING_32(Netgame.title); val = (ubyte)multi_num_players(); ADD_DATA(val); // add the proper flags flags = 0; if ( (Netgame.mode == NG_MODE_PASSWORD) || ((Game_mode & GM_STANDALONE_SERVER) && (multi_num_players() == 0) && (std_is_host_passwd())) ) { flags |= AG_FLAG_PASSWD; } // proper netgame type flags if(Netgame.type_flags & NG_TYPE_TEAM){ flags |= AG_FLAG_TEAMS; } else if(Netgame.type_flags & NG_TYPE_DOGFIGHT){ flags |= AG_FLAG_DOGFIGHT; } else { flags |= AG_FLAG_COOP; } // proper netgame state flags switch(Netgame.game_state){ case NETGAME_STATE_FORMING: flags |= AG_FLAG_FORMING; break; case NETGAME_STATE_BRIEFING: case NETGAME_STATE_MISSION_SYNC: case NETGAME_STATE_HOST_SETUP: flags |= AG_FLAG_BRIEFING; break; case NETGAME_STATE_IN_MISSION: flags |= AG_FLAG_IN_MISSION; break; case NETGAME_STATE_PAUSED: flags |= AG_FLAG_PAUSE; break; case NETGAME_STATE_ENDGAME: case NETGAME_STATE_DEBRIEF: flags |= AG_FLAG_DEBRIEF; break; } // if this is a standalone if(Game_mode & GM_STANDALONE_SERVER){ flags |= AG_FLAG_STANDALONE; } // if we're in campaign mode if(Netgame.campaign_mode == MP_CAMPAIGN){ flags |= AG_FLAG_CAMPAIGN; } // if we're registered on multi tracker if (Net_player->flags & NETINFO_FLAG_MT_CONNECTED) { flags |= AG_FLAG_TRACKER; } // add the data about the connection speed of the host machine Assert( (Multi_connection_speed >= 0) && (Multi_connection_speed <= 4) ); flags |= (Multi_connection_speed << AG_FLAG_CONNECTION_BIT); ADD_USHORT(flags); // send the data psnet_send(addr, data, packet_size); } // process information about an active game void process_game_active_packet(ubyte* data, header* hinfo) { int offset; ubyte val; active_game ag; int modes_compatible = 1; fill_net_addr(&ag.server_addr, hinfo->addr, hinfo->port); // read this game into a temporary structure offset = HEADER_LENGTH; // get the server version and compatible version GET_DATA(ag.version); GET_DATA(ag.comp_version); // use 4-byte version of *_STRING to remain compatible with older builds GET_STRING_32(ag.name); GET_STRING_32(ag.mission_name); GET_STRING_32(ag.title); GET_DATA(val); ag.num_players = val; GET_USHORT(ag.flags); PACKET_SET_SIZE(); if ( (ag.flags & AG_FLAG_TRACKER) && !Multi_options_g.pxo ) modes_compatible = 0; if ( !(ag.flags & AG_FLAG_TRACKER) && Multi_options_g.pxo ) modes_compatible = 0; // if this is a compatible version, and our modes are compatible, register it if ( (ag.version == MULTI_FS_SERVER_VERSION) && modes_compatible ) { if (gameseq_get_state() == GS_STATE_MULTI_JOIN_GAME) { multi_join_maybe_update_selected(&ag); } multi_update_active_games(&ag); } } //********************************************************************************************************* // Game Update Packet //********************************************************************************************************* /* struct fs2_game_update { char packet_signature; int len1; char netgame_name[len1]; int len2; char netgame_mission_name[len2]; int len3; char netgame_title[len3]; int len4; char netgame_campaign_name[len4]; int campaign_mode; int max_players; int security; unsigned int respawn; int flags; int type_flags; int version_info; ubyte debug_flags; // !!!!!! this isn't relying on information earlier in the packet! // receiving seems to always assume it's there! if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ int game_state; } } */ // send_game_update_packet sends an updated Netgame structure to all players currently connected. The update // is used to change the current mission, current state, etc. void send_netgame_update_packet(net_player *pl, const bool interval) { int packet_size; int idx; ubyte data[MAX_PACKET_SIZE]; packet_size = 0; BUILD_HEADER(GAME_UPDATE); // with new mission description field, this becomes way to large // so we must add every element piece by piece except the ADD_STRING(Netgame.name); ADD_STRING(Netgame.mission_name); ADD_STRING(Netgame.title); ADD_STRING(Netgame.campaign_name); ADD_INT(Netgame.campaign_mode); ADD_INT(Netgame.max_players); ADD_INT(Netgame.security); ADD_UINT(Netgame.respawn); ADD_INT(Netgame.flags); ADD_INT(Netgame.type_flags); ADD_INT(Netgame.version_info); ADD_DATA(Netgame.debug_flags); // only the server should ever send the netgame state (standalone situation) if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ ADD_INT(Netgame.game_state); } // if we're the host on a standalone, send to the standalone and let him rebroadcast if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ if ( pl == NULL ) { multi_io_send_to_all_reliable(data, packet_size); for(idx=0; idxp_info.addr , 1 ); } } else { Assert( pl == NULL ); // I don't think that a host in a standalone game would get here. multi_io_send_reliable(Net_player, data, packet_size); } // host should always send a netgame options update as well if(Net_player->flags & NETINFO_FLAG_GAME_HOST){ multi_options_update_netgame(); } // update tracker as well if ( (Net_player->flags & NETINFO_FLAG_MT_CONNECTED) && !interval ) { multi_fs_tracker_update_game(&Netgame); } } // process information about the netgame sent from the server/host void process_netgame_update_packet( ubyte *data, header *hinfo ) { int offset; int ng_state; Assert(!(Game_mode & GM_STANDALONE_SERVER)); Assert(!(Net_player->flags & NETINFO_FLAG_AM_MASTER)); // read in the netgame information offset = HEADER_LENGTH; GET_STRING(Netgame.name); GET_STRING(Netgame.mission_name); GET_STRING(Netgame.title); GET_STRING(Netgame.campaign_name); GET_INT(Netgame.campaign_mode); GET_INT(Netgame.max_players); // ignore on the standalone, who keeps track of this himself GET_INT(Netgame.security); GET_UINT(Netgame.respawn); // be sure not to blast the quitting flag because of the "one frame extra" problem GET_INT(Netgame.flags); GET_INT(Netgame.type_flags); GET_INT(Netgame.version_info); GET_DATA(Netgame.debug_flags); // netgame state GET_INT(ng_state); PACKET_SET_SIZE(); // now compare the passed in game state to our current known state. If it has changed, then maybe // do something interesting. // move from the forming or debriefing state to the mission sync state if ( ng_state == NETGAME_STATE_MISSION_SYNC ){ // if coming from the forming state if( (Netgame.game_state == NETGAME_STATE_FORMING) || ((Netgame.game_state != NETGAME_STATE_FORMING) && ((gameseq_get_state() == GS_STATE_MULTI_HOST_SETUP) || (gameseq_get_state() == GS_STATE_MULTI_CLIENT_SETUP))) ){ // do any special processing for forced state transitions multi_handle_state_special(); Multi_sync_mode = MULTI_SYNC_PRE_BRIEFING; strcpy_s( Game_current_mission_filename, Netgame.mission_name ); gameseq_post_event(GS_EVENT_MULTI_MISSION_SYNC); } // if coming from the debriefing state else if( (Netgame.game_state == NETGAME_STATE_DEBRIEF) || ((Netgame.game_state != NETGAME_STATE_DEBRIEF) && ((gameseq_get_state() == GS_STATE_DEBRIEF) || (gameseq_get_state() == GS_STATE_MULTI_DOGFIGHT_DEBRIEF)) ) ){ // do any special processing for forced state transitions multi_handle_state_special(); multi_flush_mission_stuff(); Multi_sync_mode = MULTI_SYNC_PRE_BRIEFING; strcpy_s( Game_current_mission_filename, Netgame.mission_name ); gameseq_post_event(GS_EVENT_MULTI_MISSION_SYNC); } } // move from mission sync to team select else if ( ng_state == NETGAME_STATE_BRIEFING ){ if( (Netgame.game_state == NETGAME_STATE_MISSION_SYNC) || ((Netgame.game_state != NETGAME_STATE_MISSION_SYNC) && (gameseq_get_state() == GS_STATE_MULTI_MISSION_SYNC) && (Multi_sync_mode != MULTI_SYNC_POST_BRIEFING)) ){ // do any special processing for forced state transitions multi_handle_state_special(); strcpy_s( Game_current_mission_filename, Netgame.mission_name ); extern bool mission_has_fiction(); extern int mission_has_cmd_brief(); extern int red_alert_mission(); if ( mission_has_fiction() ) { gameseq_post_event(GS_EVENT_FICTION_VIEWER); } else if ( mission_has_cmd_brief() ) { gameseq_post_event(GS_EVENT_CMD_BRIEF); } else if ( red_alert_mission() ) { gameseq_post_event(GS_EVENT_RED_ALERT); } else { gameseq_post_event(GS_EVENT_START_BRIEFING); } } } // move from the debriefing to the create game screen else if ( ng_state == NETGAME_STATE_FORMING ){ if( (Netgame.game_state == NETGAME_STATE_DEBRIEF) || ((Netgame.game_state != NETGAME_STATE_DEBRIEF) && ((gameseq_get_state() == GS_STATE_DEBRIEF) || (gameseq_get_state() == GS_STATE_MULTI_DOGFIGHT_DEBRIEF)) ) ){ // do any special processing for forced state transitions multi_handle_state_special(); multi_flush_mission_stuff(); // move to the proper screen if(Net_player->flags & NETINFO_FLAG_GAME_HOST){ gameseq_post_event(GS_EVENT_MULTI_HOST_SETUP); } else { gameseq_post_event(GS_EVENT_MULTI_CLIENT_SETUP); } } } Netgame.game_state = ng_state; } //********************************************************************************************************* // Game Update Packet //********************************************************************************************************* /* */ // send a request or a reply for mission description, if code == 0, request, if code == 1, reply // Cyborg17 - Now recursive for longer descriptions! void send_netgame_descript_packet(net_addr *addr, int code) { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; // Get this out of the way, because we don't want to waste our time if the addr is bad. Assertion(addr != nullptr, "Net address not specified!"); if (addr == nullptr) { return; } // if we are the client requesting, just send if ( !code ) { // build the header BUILD_HEADER(UPDATE_DESCRIPT); ubyte val = 0; ADD_DATA(val); psnet_send(addr, data, packet_size); } // otherwise, break up description string if necessary and send to client else { extern SCP_string Multi_netgame_common_description; SCP_string message; const size_t MAX_LEN = MAX_PACKET_SIZE - HEADER_LENGTH - 10; const size_t desc_len = Multi_netgame_common_description.length(); size_t desc_offset = 0; ubyte index = 1; // to indicate which message index this is for concatenation - 1, 2, ... do { message = Multi_netgame_common_description.substr(desc_offset, std::min(desc_len, MAX_LEN)); BUILD_HEADER(UPDATE_DESCRIPT); ADD_DATA(index); ADD_STRING_16(message.c_str()); psnet_send(addr, data, packet_size); ++index; desc_offset += message.length(); } while (desc_offset < desc_len); } } // process an incoming netgame description packet void process_netgame_descript_packet( ubyte *data, header *hinfo ) { int offset, state; ubyte code; net_addr addr; fill_net_addr(&addr, hinfo->addr, hinfo->port); // read this game into a temporary structure offset = HEADER_LENGTH; GET_DATA(code); // if this is a request for mission description if(code == 0){ if(!(Net_player->flags & NETINFO_FLAG_AM_MASTER)){ PACKET_SET_SIZE(); return; } // send an update to this guy send_netgame_descript_packet(&addr, 1); } // otherwise 'code' will be an index indicating the message number - 1, 2, ... else { char message[MAX_PACKET_SIZE]; GET_STRING_16(message); // only display if we're in the proper state, since the client doesn't // decide when this packet arrives state = gameseq_get_state(); switch (state) { case GS_STATE_MULTI_JOIN_GAME: case GS_STATE_MULTI_CLIENT_SETUP: case GS_STATE_MULTI_HOST_SETUP: multi_mission_desciption_set(message, code); break; } } PACKET_SET_SIZE(); } // broadcast a query for active games. TCP will either request from the MT or from the specified list void broadcast_game_query() { server_item *s_moveup; if (MULTI_IS_TRACKER_GAME) { // check with MT multi_fs_tracker_send_game_request(); return; } else { // go through the server list and query each of those as well s_moveup = Game_server_head; if (s_moveup != nullptr) { do { send_server_query(&s_moveup->server_addr); s_moveup = s_moveup->next; } while (s_moveup != Game_server_head); } } // send out a broadcast if our options allow us if(Net_player->p_info.options.flags & MLO_FLAG_LOCAL_BROADCAST){ multi_mdns_query(); } } // send an individual query to an address to see if there is an active game void send_server_query(net_addr *addr) { int packet_size; ubyte data[MAX_PACKET_SIZE]; // build the header and send the data BUILD_HEADER(GAME_QUERY); psnet_send(addr, data, packet_size); } // process a query from a client looking for active freespace games void process_game_query(ubyte* /*data*/, header* hinfo) { int offset; net_addr addr; offset = HEADER_LENGTH; PACKET_SET_SIZE(); // if I am not a server of a game, don't send a reply!!! if ( !(Net_player->flags & NETINFO_FLAG_AM_MASTER) ){ return; } // if the game options are being selected, then ignore the request // also, if Netgame.max_players == -1, the host has not chosen a mission yet and we should wait if((Netgame.game_state == NETGAME_STATE_STD_HOST_SETUP) || (Netgame.game_state == NETGAME_STATE_HOST_SETUP) || (Netgame.game_state == 0) || (Netgame.max_players == -1)){ return; } // check to be sure that we don't capture our own broadcast message fill_net_addr(&addr, hinfo->addr, hinfo->port); if ( psnet_is_local_addr(&addr) ) { return; } // send information about this active game send_game_active_packet(&addr); } // sends information about netplayers in the game. if called on the server, broadcasts information about _all_ players void send_netplayer_update_packet( net_player *pl ) { int packet_size,idx; ubyte data[MAX_PACKET_SIZE],val; BUILD_HEADER(NETPLAYER_UPDATE); // if I'm the server of the game, I should send an update for _all_players in the game if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ for(idx=0;idxplayer_id); ADD_INT(Net_player->state); ADD_INT(Net_player->p_info.ship_class); ADD_INT(Multi_tracker_id); // Used to check or a CD here but that has been removed --Mjn val = 1; ADD_DATA(val); // add a final stop byte val = 0xff; ADD_DATA(val); // send the packet to the server Assert( pl == NULL ); // shouldn't ever be the case that pl is non-null here. if(!(Game_mode & GM_IN_MISSION)){ multi_io_send_reliable(Net_player, data, packet_size); } else { multi_io_send(Net_player, data, packet_size); } } } // process an incoming netplayer state update. if we're the server, we should rebroadcast void process_netplayer_update_packet( ubyte *data, header *hinfo ) { int offset, player_num; net_player bogus; ubyte stop, has_cd; short player_id; int new_state; offset = HEADER_LENGTH; // get the first stop byte GET_DATA(stop); player_num = -1; while(stop != 0xff){ // look the player up GET_SHORT(player_id); player_num = find_player_index(player_id); // if we couldn't find him, read in the bogus data if((player_num == -1) || (Net_player == &Net_players[player_num])){ GET_INT(bogus.state); GET_INT(bogus.p_info.ship_class); GET_INT(bogus.tracker_player_id); GET_DATA(has_cd); } // otherwise read in the data correctly else { GET_INT(new_state); GET_INT(Net_players[player_num].p_info.ship_class); GET_INT(Net_players[player_num].tracker_player_id); GET_DATA(has_cd); //unused // if he's changing state to joined, send a team update if((Net_players[player_num].state == NETPLAYER_STATE_JOINING) && (new_state == NETPLAYER_STATE_JOINED) && (Netgame.type_flags & NG_TYPE_TEAM)){ multi_team_send_update(); } // set state Net_players[player_num].state = new_state; } // get the next stop byte GET_DATA(stop); } PACKET_SET_SIZE(); // if I'm the host or the server of the game, update everyone else so things are synched up as tightly as possible if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ send_netplayer_update_packet(NULL); } // if i'm the standalone and this is an update from the host, maybe change some netgame settings if((Game_mode & GM_STANDALONE_SERVER) && (player_num != -1) && (Net_players[player_num].flags & NETINFO_FLAG_GAME_HOST)){ switch(Net_players[player_num].state){ case NETPLAYER_STATE_STD_HOST_SETUP: Netgame.game_state = NETGAME_STATE_STD_HOST_SETUP; break; case NETPLAYER_STATE_HOST_SETUP: // check for race conditions if(Netgame.game_state != NETGAME_STATE_MISSION_SYNC){ Netgame.game_state = NETGAME_STATE_FORMING; } break; } } } #define EXTRA_DEATH_VAPORIZED (1<<0) #define EXTRA_DEATH_WASHED (1<<1) // send a packet indicating a ship has been killed void send_ship_kill_packet( const object *objp, const object *other_objp, float percent_killed, int self_destruct ) { int packet_size, model; ubyte data[MAX_PACKET_SIZE], was_player, extra_death_info, vaporized; ushort debris_signature; ubyte sd; polymodel * pm; // only sendable from the master Assert ( Net_player->flags & NETINFO_FLAG_AM_MASTER ); // special deaths vaporized = Ships[objp->instance].flags[Ship::Ship_Flags::Vaporize]; extra_death_info = 0; if ( vaporized ) { extra_death_info |= EXTRA_DEATH_VAPORIZED; } if ( Ships[objp->instance].wash_killed ) { extra_death_info |= EXTRA_DEATH_WASHED; } // find out the next network signature that will be used for the debris pieces. model = Ship_info[Ships[objp->instance].ship_info_index].model_num; pm = model_get(model); debris_signature = 0; if ( pm && !vaporized ) { debris_signature = multi_get_next_network_signature( MULTI_SIG_DEBRIS ); multi_set_network_signature( (ushort)(debris_signature + pm->num_debris_objects), MULTI_SIG_DEBRIS ); Ships[objp->instance].debris_net_sig = debris_signature; } BUILD_HEADER(SHIP_KILL); ADD_USHORT(objp->net_signature); // ships which are initially killed get the rest of the data sent. self destructed ships and if ( other_objp == NULL ) { ushort temp; temp = 0; ADD_USHORT(temp); nprintf(("Network","Don't know other_obj for ship kill packet, sending NULL\n")); } else { ADD_USHORT( other_objp->net_signature ); } ADD_USHORT( debris_signature ); ADD_FLOAT( percent_killed ); sd = (ubyte)self_destruct; ADD_DATA(sd); ADD_DATA( extra_death_info ); // if the ship who died is a player, then send some extra info, like who killed him, etc. was_player = 0; if ( objp->flags[Object::Object_Flags::Player_ship] ) { int pnum; char temp; short temp2; pnum = multi_find_player_by_object( objp ); if ( pnum != -1 ) { was_player = 1; ADD_DATA( was_player ); Assert(Net_players[pnum].m_player->killer_objtype < CHAR_MAX); temp = (char)Net_players[pnum].m_player->killer_objtype; ADD_DATA( temp ); Assert(Net_players[pnum].m_player->killer_species < CHAR_MAX); temp = (char)Net_players[pnum].m_player->killer_species; ADD_DATA( temp ); Assert(Net_players[pnum].m_player->killer_weapon_index < SHRT_MAX); temp2 = (short)Net_players[pnum].m_player->killer_weapon_index; ADD_SHORT( temp2 ); ADD_STRING( Net_players[pnum].m_player->killer_parent_name ); } else { ADD_DATA( was_player ); } } else { ADD_DATA( was_player ); } // send the packet reliably!!! multi_io_send_to_all_reliable(data, packet_size); } // process a packet indicating that a ship has been killed void process_ship_kill_packet( ubyte *data, header *hinfo ) { int offset; ushort ship_sig, other_sig, debris_sig; object *sobjp, *oobjp; float percent_killed; ubyte was_player, extra_death_info, sd; char killer_name[NAME_LENGTH], killer_objtype = OBJ_NONE, killer_species = 0; short killer_weapon_index = -1; offset = HEADER_LENGTH; GET_USHORT(ship_sig); GET_USHORT( other_sig ); GET_USHORT( debris_sig ); GET_FLOAT( percent_killed ); GET_DATA( sd ); GET_DATA( extra_death_info ); GET_DATA( was_player ); // pnum is >=0 when the dying ship is a pleyer ship. Get the info about how he died if ( was_player != 0 ) { GET_DATA( killer_objtype ); GET_DATA( killer_species ); GET_SHORT( killer_weapon_index ); GET_STRING( killer_name ); } PACKET_SET_SIZE(); sobjp = multi_get_network_object( ship_sig ); // if I am unable to find the ship object which was killed, I have to bail and rely on getting // another message from the server that this happened! if ( sobjp == NULL ) { nprintf(("Network", "Couldn't find net signature %d for kill packet\n", ship_sig)); return; } // set this ship's hull value to 0 sobjp->hull_strength = 0.0f; // maybe set vaporized if (extra_death_info & EXTRA_DEATH_VAPORIZED) { Ships[sobjp->instance].flags.set(Ship::Ship_Flags::Vaporize); } // maybe set wash_killed if (extra_death_info & EXTRA_DEATH_WASHED) { Ships[sobjp->instance].wash_killed = 1; } oobjp = multi_get_network_object( other_sig ); if ( was_player != 0 ) { int pnum; pnum = multi_find_player_by_object( sobjp ); if ( pnum != -1 ) { Net_players[pnum].m_player->killer_objtype = killer_objtype; Net_players[pnum].m_player->killer_species = killer_species; Net_players[pnum].m_player->killer_weapon_index = killer_weapon_index; strcpy_s( Net_players[pnum].m_player->killer_parent_name, killer_name ); } } // check to see if I need to respawn myself multi_respawn_check(sobjp); // store the debris signature in the arrival distance which will never get used for player ships Ships[sobjp->instance].debris_net_sig = debris_sig; // set this bit so that we don't accidentally start switching targets when we die if(sobjp == Player_obj){ Game_mode |= GM_DEAD_DIED; } mprintf(("Network Killing off %s\n", Ships[sobjp->instance].ship_name)); // do the normal thing when not ingame joining. When ingame joining, simply kill off the ship. if ( !(Net_player->flags & NETINFO_FLAG_INGAME_JOIN) ) { ship_hit_kill( sobjp, oobjp, nullptr, percent_killed, sd != 0 ); } else { sobjp->flags.set(Object::Object_Flags::Should_be_dead); ship_cleanup(sobjp->instance, SHIP_DESTROYED); obj_delete( OBJ_INDEX(sobjp) ); } } constexpr int WK_FLAGS_WEAPON_WEAPON = (1<<0); // simply removes a missile from existence on the client. void send_missile_kill_packet(object* objp) { Assert(objp != nullptr); Assert(objp->type == OBJ_WEAPON); Assert(MULTIPLAYER_MASTER); if (objp == nullptr || objp->type != OBJ_WEAPON || MULTIPLAYER_CLIENT || !(Weapon_info[Weapons[objp->instance].weapon_info_index].subtype & WP_MISSILE)) { return; } ubyte data[MAX_PACKET_SIZE], flags = 0; int packet_size; BUILD_HEADER(MISSILE_KILL); ADD_USHORT(objp->net_signature); if (objp->instance > -1) { if (Weapons[objp->instance].weapon_flags[Weapon::Weapon_Flags::Destroyed_by_weapon]) flags |= WK_FLAGS_WEAPON_WEAPON; } ADD_DATA(flags); multi_io_send_to_all_reliable(data, packet_size); } void process_weapon_kill_packet(ubyte *data, header *hinfo ) { // only masters should receive this packet. Assert(MULTIPLAYER_CLIENT); int offset = HEADER_LENGTH; ushort missile_net_signature; ubyte flags; GET_USHORT(missile_net_signature); GET_DATA(flags); PACKET_SET_SIZE(); // get the object pointer for the missile object* missile = multi_get_network_object(missile_net_signature); // make sure it's valid before implementing. if (missile == nullptr || missile->type != OBJ_WEAPON || missile->instance < 0 || !(Weapon_info[Weapons[missile->instance].weapon_info_index].subtype & WP_MISSILE)) { return; } // set any optional packets. if (flags & WK_FLAGS_WEAPON_WEAPON) { Weapons[missile->instance].weapon_flags.set(Weapon::Weapon_Flags::Destroyed_by_weapon); } // kill the missile! if (Weapon_info[Weapons[missile->instance].weapon_info_index].weapon_hitpoints > 0.0f) { missile->hull_strength = -1.0f; } weapon_detonate(missile); } // send a packet indicating a ship should be created void send_ship_create_packet( object *objp, bool is_support ) { int packet_size; ubyte data[MAX_PACKET_SIZE]; ubyte val; // We will pass the ship to create by name. BUILD_HEADER(SHIP_CREATE); ADD_USHORT(objp->net_signature); val = is_support ? 1 : 0; ADD_DATA(val); if ( is_support ){ ADD_VECTOR( objp->pos ); } // broadcast the packet multi_io_send_to_all_reliable(data, packet_size); } // process a packet indicating a ship should be created void process_ship_create_packet( ubyte *data, header *hinfo ) { int offset, objnum; ushort signature; p_object *objp; vec3d pos = ZERO_VECTOR; ubyte is_support; Assert ( !(Net_player->flags & NETINFO_FLAG_AM_MASTER) ); offset = HEADER_LENGTH; GET_USHORT(signature); GET_DATA(is_support); if ( is_support ){ GET_VECTOR( pos ); } PACKET_SET_SIZE(); // find the name of this ship on ship ship arrival list. if found, pass it to parse_object_create if ( !is_support ) { objp = mission_parse_get_arrival_ship( signature ); if ( objp != NULL ) { objnum = parse_create_object(objp); } else { nprintf(("Network", "Ship with sig %d not found on ship arrival list -- not creating!!\n", signature)); } } else { Assert( Arriving_support_ship ); if(Arriving_support_ship == NULL){ return; } Arriving_support_ship->pos = pos; Arriving_support_ship->net_signature = signature; objnum = parse_create_object( Arriving_support_ship ); Assert( objnum != -1 ); if(objnum >= 0){ mission_parse_support_arrived( objnum ); } } } // send a packet indicating a wing of ships should be created void send_wing_create_packet( wing *wingp, int num_to_create, int pre_create_count ) { int packet_size, index, ship_instance; ubyte data[MAX_PACKET_SIZE]; ushort signature; int val; // for creating wing -- we just send the index into the wing array of this wing. // all players load the same mission, and so their array's should all match. We also // need to send the signature of the first ship that was created. We can find this by // looking num_to_create places back in the ship_index field in the wing structure. index = WING_INDEX(wingp); ship_instance = wingp->ship_index[wingp->current_count - num_to_create]; signature = Objects[Ships[ship_instance].objnum].net_signature; BUILD_HEADER( WING_CREATE ); ADD_INT(index); ADD_INT(num_to_create); ADD_USHORT(signature); ADD_INT(pre_create_count); val = wingp->current_wave - 1; ADD_INT(val); multi_io_send_to_all_reliable(data, packet_size); } // process a packet saying that a wing should be created void process_wing_create_packet( ubyte *data, header *hinfo ) { int offset, index, num_to_create; ushort signature; int total_arrived_count, current_wave; offset = HEADER_LENGTH; GET_INT(index); GET_INT(num_to_create); GET_USHORT(signature); GET_INT(total_arrived_count); GET_INT(current_wave); PACKET_SET_SIZE(); // do a sanity check on the wing to be sure that we are actually working on a valid wing if ( (index < 0) || (index >= Num_wings) || (Wings[index].num_waves == -1) ) { nprintf(("Network", "Invalid index %d for wing create packet\n", index)); return; } if ( (num_to_create <= 0) || (num_to_create > Wings[index].wave_count) ) { nprintf(("Network", "Invalid number of ships to create (%d) for wing %s\n", num_to_create, Wings[index].name)); return; } // bash some info Wings[index].current_count = 0; Wings[index].total_arrived_count = total_arrived_count; Wings[index].current_wave = current_wave; // set the network signature that was passed. The client should create ships in the same order // as the server -- so all ships should get the same sigs as assigned by the server. We also // need to set some timestamps and cues correctly to be sure that these things get created on // the clients correctly multi_set_network_signature( signature, MULTI_SIG_SHIP ); parse_wing_create_ships( &Wings[index], num_to_create, true ); } // packet indicating a ship is departing void send_ship_depart_packet( object *objp, int method ) { ubyte data[MAX_PACKET_SIZE]; int packet_size; ushort signature; signature = objp->net_signature; BUILD_HEADER(SHIP_DEPART); ADD_USHORT( signature ); ADD_INT( method ); multi_io_send_to_all_reliable(data, packet_size); } // process a packet indicating a ship is departing void process_ship_depart_packet( ubyte *data, header *hinfo ) { int offset; object *objp; ushort signature; int s_method; offset = HEADER_LENGTH; GET_USHORT( signature ); GET_INT(s_method); PACKET_SET_SIZE(); // find the object which is departing objp = multi_get_network_object( signature ); if ( objp == NULL ) { nprintf(("network", "Couldn't find object with net signature %d to depart\n", signature )); return; } switch (s_method) { case SHIP_DEPARTED_BAY: case SHIP_VANISHED: if (objp->type == OBJ_SHIP) { ship_actually_depart(objp->instance, s_method); } else { nprintf(("network", "Can not process ship depart packed. Object with net signature %d is not a ship!\n", signature )); return; } break; // assume standard warp out default: // start warping him out shipfx_warpout_start( objp ); } } // packet to tell clients cargo of a ship was revealed to all void send_cargo_revealed_packet( ship *shipp ) { ubyte data[MAX_PACKET_SIZE]; int packet_size; // build the header and add the data BUILD_HEADER(CARGO_REVEALED); ADD_USHORT( Objects[shipp->objnum].net_signature ); // server sends to all players if(MULTIPLAYER_MASTER){ multi_io_send_to_all_reliable(data, packet_size); } // clients just send to the server else { multi_io_send_reliable(Net_player, data, packet_size); } } // process a cargo revealed packet void process_cargo_revealed_packet( ubyte *data, header *hinfo ) { int offset; ushort signature; object *objp; offset = HEADER_LENGTH; GET_USHORT(signature); PACKET_SET_SIZE(); // get a ship pointer and call the ship function to reveal the cargo objp = multi_get_network_object( signature ); if ( objp == NULL ) { nprintf(("Network", "Could not find object with net signature %d for cargo revealed\n", signature )); return; } // Assert( objp->type == OBJ_SHIP ); if((objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS)){ return; } // this will take care of re-routing to all other clients ship_do_cargo_revealed( &Ships[objp->instance], 1); // server should rebroadcast if(MULTIPLAYER_MASTER){ send_cargo_revealed_packet(&Ships[objp->instance]); } } // packet to tell clients cargo of a ship was hidden to all void send_cargo_hidden_packet( ship *shipp ) { ubyte data[MAX_PACKET_SIZE]; int packet_size; // build the header and add the data BUILD_HEADER(CARGO_HIDDEN); ADD_USHORT( Objects[shipp->objnum].net_signature ); // server sends to all players if(MULTIPLAYER_MASTER){ multi_io_send_to_all_reliable(data, packet_size); } // clients just send to the server else { multi_io_send_reliable(Net_player, data, packet_size); } } // process a cargo hidden packet void process_cargo_hidden_packet( ubyte *data, header *hinfo ) { int offset; ushort signature; object *objp; offset = HEADER_LENGTH; GET_USHORT(signature); PACKET_SET_SIZE(); // get a ship pointer and call the ship function to hide the cargo objp = multi_get_network_object( signature ); if ( objp == NULL ) { nprintf(("Network", "Could not find object with net signature %d for cargo hidden\n", signature )); return; } // Assert( objp->type == OBJ_SHIP ); if((objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS)){ return; } // this will take care of re-routing to all other clients ship_do_cargo_hidden( &Ships[objp->instance], 1); // server should rebroadcast if(MULTIPLAYER_MASTER){ send_cargo_hidden_packet(&Ships[objp->instance]); } } // defines used for secondary fire packet #define SFPF_ALLOW_SWARM (1<<7) #define SFPF_DUAL_FIRE (1<<6) #define SFPF_TARGET_LOCKED (1<<5) // send a packet indicating a secondary weapon was fired void send_secondary_fired_packet( ship *shipp, ushort starting_sig, tracking_info &tinfo, int num_fired, int allow_swarm ) { int packet_size, net_player_num; ubyte data[MAX_PACKET_SIZE], sinfo, current_bank; object *objp; ushort target_net_signature; int s_index; // Assert ( starting_count < UCHAR_MAX ); // get the object for this ship. If it is an AI object, send all the info to all player. Otherwise, // we might send the info to the other player different than the one who fired objp = &Objects[shipp->objnum]; if ( !(objp->flags[Object::Object_Flags::Player_ship]) ) { if ( num_fired == 0 ) { return; } } current_bank = (ubyte)shipp->weapons.current_secondary_bank; Assert( (current_bank < MAX_SHIP_SECONDARY_BANKS) ); // build up the header portion BUILD_HEADER( SECONDARY_FIRED_AI ); ADD_USHORT( Objects[shipp->objnum].net_signature ); ADD_USHORT( starting_sig ); // add a couple of bits for swarm missiles and dual fire secondary weapons sinfo = current_bank; if ( allow_swarm ){ sinfo |= SFPF_ALLOW_SWARM; } if ( shipp->flags[Ship::Ship_Flags::Secondary_dual_fire] ){ sinfo |= SFPF_DUAL_FIRE; } if ( tinfo.locked ){ sinfo |= SFPF_TARGET_LOCKED; } ADD_DATA( sinfo ); // add the ship's target and any targeted subsystem target_net_signature = 0; s_index = -1; if (tinfo.objnum != -1) { target_net_signature = Objects[tinfo.objnum].net_signature; if ( (Objects[tinfo.objnum].type == OBJ_SHIP) && (tinfo.subsys != nullptr) ) { s_index = ship_get_subsys_index( tinfo.subsys ); } if ( Objects[tinfo.objnum].type == OBJ_WEAPON ) { Assert(Weapon_info[Weapons[Objects[tinfo.objnum].instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Bomb]); } } ADD_USHORT( target_net_signature ); ADD_SHORT( static_cast(s_index) ); // just send this packet to everyone, then bail if an AI ship fired. if ( !(objp->flags[Object::Object_Flags::Player_ship]) ) { multi_io_send_to_all(data, packet_size); return; } net_player_num = multi_find_player_by_object( objp ); if ( net_player_num < 0 ) { // Pass to higher level code to handle return; } // getting here means a player fired. Send the current packet to all players except the player // who fired. If nothing got fired, then don't send to the other players -- we will just send // a packet to the player who will find out that he didn't fire anything if ( num_fired > 0 ) { multi_io_send_to_all_reliable(data, packet_size, &Net_players[net_player_num]); } // if I (the master) fired, then return if ( Net_players[net_player_num].flags & NETINFO_FLAG_AM_MASTER ){ return; } // if this is a rollback shot, the firing player does not need this packet. if (multi_ship_record_get_rollback_wep_mode()) { return; } // if this is a dumbfire weapon then skip it since it's client fired if ( !Weapon_info[shipp->weapons.secondary_bank_weapons[current_bank]].is_homing() ) { return; } // now build up the packet to send to the player who actually fired. BUILD_HEADER( SECONDARY_FIRED_PLR ); ADD_USHORT(starting_sig); ADD_DATA( sinfo ); // add the targeting information so that the player's weapons will always home on the correct // ship ADD_USHORT( target_net_signature ); ADD_SHORT( static_cast(s_index) ); multi_io_send_reliable(&Net_players[net_player_num], data, packet_size); } /// process a packet indicating a secondary weapon was fired void process_secondary_fired_packet(ubyte* data, header* hinfo, int from_player) { int offset, allow_swarm, target_objnum_save; ushort net_signature, starting_sig, target_net_signature; ubyte sinfo, current_bank; object* objp, *target_objp; ship *shipp; short t_subsys; ai_info *aip; ship_subsys *targeted_subsys_save; offset = HEADER_LENGTH; // size of the header // if from_player is false, it means that the secondary weapon info in this packet was // fired by an ai object (or another player). from_player == 1 means tha me (the person // receiving this packet) fired the secondary weapon if ( !from_player ) { GET_USHORT( net_signature ); GET_USHORT( starting_sig ); GET_DATA( sinfo ); // includes flags and the secondary bank. GET_USHORT( target_net_signature ); GET_SHORT( t_subsys ); PACKET_SET_SIZE(); // find the object (based on network signatures) for the object that fired objp = multi_get_network_object( net_signature ); if ( objp == NULL ) { nprintf(("Network", "Could not find ship for fire secondary packet!\n")); return; } // set up the ships current secondary bank and that bank's mode. Below, we will set the timeout // of the next fire time of this bank to 0 so we can fire right away shipp = &Ships[objp->instance]; } else { GET_USHORT( starting_sig ); GET_DATA( sinfo ); GET_USHORT( target_net_signature ); GET_SHORT( t_subsys ); PACKET_SET_SIZE(); // get the object and ship objp = Player_obj; shipp = Player_ship; } // check the allow swarm bit allow_swarm = 0; if ( sinfo & SFPF_ALLOW_SWARM ){ allow_swarm = 1; } // set the dual fire properties of the ship shipp->flags.set(Ship::Ship_Flags::Secondary_dual_fire, (sinfo & SFPF_DUAL_FIRE) != 0); // determine whether current target is locked Assert( shipp->ai_index != -1 ); aip = &Ai_info[shipp->ai_index]; if ( sinfo & SFPF_TARGET_LOCKED ) { aip->current_target_is_locked = 1; } else { aip->current_target_is_locked = 0; } // find out the current bank current_bank = (ubyte)(sinfo & 0x3); Assert( (current_bank < MAX_SHIP_SECONDARY_BANKS) ); shipp->weapons.current_secondary_bank = current_bank; // make it so we can fire this ship's secondary bank immediately!!! shipp->weapons.next_secondary_fire_stamp[shipp->weapons.current_secondary_bank] = timestamp(0); shipp->weapons.detonate_weapon_time = timestamp(5000); // be sure that we don't detonate a remote weapon before it is time. // set this ship's target and subsystem information. We will save and restore target and // targeted subsystem so that we do not accidentally change targets for this player or // any AI ships on his system. target_objnum_save = aip->target_objnum; targeted_subsys_save = aip->targeted_subsys; // reset these variables for accuracy. They will get reassigned at the end of this fuction aip->target_objnum = -1; aip->targeted_subsys = NULL; target_objp = multi_get_network_object( target_net_signature ); if ( target_objp != NULL ) { aip->target_objnum = OBJ_INDEX(target_objp); if ( (t_subsys != -1) && (target_objp->type == OBJ_SHIP) ) { aip->targeted_subsys = ship_get_indexed_subsys( &Ships[target_objp->instance], t_subsys); } } if ( starting_sig != 0 ){ multi_set_network_signature( starting_sig, MULTI_SIG_NON_PERMANENT ); } else { shipp->weapons.detonate_weapon_time = timestamp(0); // signature of -1 say detonate remote weapon } ship_fire_secondary( objp, allow_swarm ); // restore targeted object and targeted subsystem aip->target_objnum = target_objnum_save; aip->targeted_subsys = targeted_subsys_save; } // send a packet indicating a countermeasure was fired void send_countermeasure_fired_packet( object *objp, int cmeasure_count, int rand_val ) { ubyte data[MAX_PACKET_SIZE]; int packet_size; Int3(); Assert ( cmeasure_count < UCHAR_MAX ); BUILD_HEADER(COUNTERMEASURE_FIRED); ADD_USHORT( objp->net_signature ); ADD_INT( rand_val ); multi_io_send_to_all(data, packet_size); } // process a packet indicating a countermeasure was fired void process_countermeasure_fired_packet( ubyte *data, header *hinfo ) { int offset, rand_val; ushort signature; object *objp; Int3(); offset = HEADER_LENGTH; GET_USHORT( signature ); GET_INT( rand_val ); PACKET_SET_SIZE(); objp = multi_get_network_object( signature ); if ( objp == NULL ) { nprintf(("network", "Could find object whose countermeasures are being launched!!!\n")); return; } if(objp->type != OBJ_SHIP){ return; } // Assert ( objp->type == OBJ_SHIP ); // make it so ship can fire right away! Ships[objp->instance].cmeasure_fire_stamp = timestamp(0); if ( objp == Player_obj ){ nprintf(("network", "firing countermeasure from my ship\n")); } ship_launch_countermeasure( objp, rand_val ); } // send a packet indicating that a turret has been fired void send_turret_fired_packet( int ship_objnum, int subsys_index, int weapon_objnum, float dist_to_target, float target_radius, float target_forward_speed ) { int packet_size; ushort pnet_signature; ubyte data[MAX_PACKET_SIZE]; object *objp; ubyte has_sig = 0; ship_subsys *ssp; // sanity if((weapon_objnum < 0) || (Objects[weapon_objnum].type != OBJ_WEAPON) || (Objects[weapon_objnum].instance < 0) || (Weapons[Objects[weapon_objnum].instance].weapon_info_index < 0)){ return; } // local setup -- be sure we are actually passing a weapon!!!! objp = &Objects[weapon_objnum]; Assert ( objp->type == OBJ_WEAPON ); if(Weapon_info[Weapons[objp->instance].weapon_info_index].subtype == WP_MISSILE){ has_sig = 1; } pnet_signature = Objects[ship_objnum].net_signature; ssp = ship_get_indexed_subsys( &Ships[Objects[ship_objnum].instance], subsys_index ); if(ssp == NULL){ return; } // build the fire turret packet. BUILD_HEADER(FIRE_TURRET_WEAPON); packet_size += multi_pack_unpack_position(1, data + packet_size, &objp->orient.vec.fvec); ADD_DATA( has_sig ); ADD_USHORT( pnet_signature ); if(has_sig){ ADD_USHORT( objp->net_signature ); } ADD_SHORT( static_cast(Weapons[objp->instance].weapon_info_index) ); ADD_SHORT( static_cast(subsys_index) ); // where we might accidentally dereference nullptrs, send 0 instead constexpr float ZERO_VALUE = 0.0f; if (ssp->submodel_instance_1 != nullptr) { ADD_FLOAT( ssp->submodel_instance_1->cur_angle ); } else { ADD_FLOAT( ZERO_VALUE ); } if (ssp->submodel_instance_2 != nullptr) { ADD_FLOAT( ssp->submodel_instance_2->cur_angle ); } else { ADD_FLOAT(ZERO_VALUE); } ADD_FLOAT(dist_to_target); ADD_FLOAT(target_radius); ADD_FLOAT(target_forward_speed); multi_io_send_to_all(data, packet_size); multi_rate_add(1, "tur", packet_size); } // process a packet indicating a turret has been fired void process_turret_fired_packet( ubyte *data, header *hinfo ) { int offset, weapon_objnum; ushort pnet_signature, wnet_signature; vec3d pos, temp; matrix orient; vec3d o_fvec; short turret_index, wid; object *objp; ship_subsys *ssp; ubyte has_sig = 0; ship *shipp; float angle1, angle2; float dist_to_target; float target_radius; float target_forward_speed; // get the data for the turret fired packet offset = HEADER_LENGTH; offset += multi_pack_unpack_position(0, data + offset, &o_fvec); GET_DATA( has_sig ); GET_USHORT( pnet_signature ); if(has_sig){ GET_USHORT( wnet_signature ); } else { wnet_signature = 0; } GET_SHORT( wid ); GET_SHORT( turret_index ); GET_FLOAT( angle1 ); GET_FLOAT( angle2 ); GET_FLOAT( dist_to_target ); GET_FLOAT( target_radius ); GET_FLOAT( target_forward_speed ); PACKET_SET_SIZE(); // move our counter forward the number of bytes we have read // if we don't have a valid weapon index then bail if ( (wid < 0) || (wid >= weapon_info_size()) ) { return; } // find the object objp = multi_get_network_object( pnet_signature ); if ( objp == NULL ) { nprintf(("network", "could find parent object with net signature %d for turret firing\n", pnet_signature)); return; } // if this isn't a ship, do nothing if ( objp->type != OBJ_SHIP ){ return; } // make an orientation matrix from the o_fvec // NOTE: o_fvec is NOT normalized due to pack/unpack altering values! vm_vector_2_matrix(&orient, &o_fvec, nullptr, nullptr); // find this turret, and set the position of the turret that just fired to be where it fired. Quite a // hack, but should be suitable. shipp = &Ships[objp->instance]; ssp = ship_get_indexed_subsys( shipp, turret_index ); if(ssp == NULL){ return; } // bash the position and orientation of the turret // but only if the submodels are not null if (ssp->submodel_instance_1 != nullptr) { ssp->submodel_instance_1->cur_angle = angle1; } if (ssp->submodel_instance_2 != nullptr) { ssp->submodel_instance_2->cur_angle = angle2; } // get the world position of the weapon ship_get_global_turret_info(objp, ssp->system_info, &pos, &temp); auto launch_curve_data = WeaponLaunchCurveData { ssp->system_info->turret_num_firing_points, dist_to_target, target_radius, target_forward_speed, }; // create the weapon object if(wnet_signature != 0){ multi_set_network_signature( wnet_signature, MULTI_SIG_NON_PERMANENT ); } weapon_objnum = weapon_create( &pos, &orient, wid, OBJ_INDEX(objp), -1, true, false, nullptr, -1, -1, launch_curve_data); if (weapon_objnum != -1) { if ( Weapon_info[wid].launch_snd.isValid() ) { snd_play_3d( gamesnd_get_game_sound(Weapon_info[wid].launch_snd), &pos, &View_position ); } } } // send a mission log item packet void send_mission_log_packet( log_entry* entry ) { int packet_size; ubyte data[MAX_PACKET_SIZE]; ubyte type; int sindex; Assert ( MULTIPLAYER_MASTER ); // get the data from the log type = (ubyte)entry->type; // do the type casting thing to save on packet space sindex = entry->index; BUILD_HEADER(MISSION_LOG_ENTRY); ADD_DATA(type); ADD_INT(entry->flags); ADD_INT(sindex); ADD_INT(entry->timestamp); // NOTE: this is a long so careful with swapping in 64-bit platforms - taylor ADD_STRING(entry->pname); ADD_STRING(entry->sname); // broadcast the packet to all players multi_io_send_to_all_reliable(data, packet_size); } // process a mission log item packet void process_mission_log_packet( ubyte *data, header *hinfo ) { int offset, flags; int sindex; ubyte type; char pname[NAME_LENGTH], sname[NAME_LENGTH]; fix timestamp; Assert ( MULTIPLAYER_CLIENT ); offset = HEADER_LENGTH; GET_DATA(type); GET_INT(flags); GET_INT(sindex); GET_INT(timestamp); // NOTE: this is a long so careful with swapping in 64-bit platforms - taylor GET_STRING(pname); GET_STRING(sname); PACKET_SET_SIZE(); mission_log_add_entry_multi( static_cast(type), pname, sname, sindex, timestamp, flags ); } // send a mission message packet void send_mission_message_packet( int id, const char *who_from, int priority, int timing, int source, int builtin_type, int multi_target, int multi_team_filter, int delay, int event_num_to_cancel) { int packet_size; ubyte data[MAX_PACKET_SIZE], up, us, utime; Assert ( Net_player->flags & NETINFO_FLAG_AM_MASTER ); Assert ( (priority >= 0) && (priority < UCHAR_MAX) ); Assert ( (timing >= 0) && (timing < UCHAR_MAX) ); up = (ubyte) priority; us = (ubyte) source; utime = (ubyte)timing; BUILD_HEADER(MISSION_MESSAGE); ADD_INT(id); ADD_STRING(who_from); ADD_DATA(up); ADD_DATA(utime); ADD_DATA(us); ADD_INT(builtin_type); ADD_INT(multi_team_filter); ADD_INT(delay); ADD_INT(event_num_to_cancel); if (multi_target == -1){ multi_io_send_to_all_reliable(data, packet_size); } else { multi_io_send_reliable(&Net_players[multi_target], data, packet_size); } } // process a mission message packet void process_mission_message_packet( ubyte *data, header *hinfo ) { int offset, id, builtin_type, delay = 0; ubyte priority, source, utiming; char who_from[NAME_LENGTH]; int multi_team_filter, event_num_to_cancel = -1; Assert( !(Net_player->flags & NETINFO_FLAG_AM_MASTER) ); offset = HEADER_LENGTH; GET_INT(id); GET_STRING(who_from); GET_DATA(priority); GET_DATA(utiming); GET_DATA(source); GET_INT(builtin_type); GET_INT(multi_team_filter); GET_INT(delay); GET_INT(event_num_to_cancel); PACKET_SET_SIZE(); // filter out some builtin ones in TvT if((builtin_type >= 0) && (Netgame.type_flags & NG_TYPE_TEAM) && (Net_player != NULL) && (Net_player->p_info.team != multi_team_filter)) { mprintf(("Builtin message of type %d filtered out in process_mission_message_packet()\n", id)); return; } // maybe filter this out if(!message_filter_multi(id)){ // send the message as if it came from an sexpression message_queue_message( id, priority, utiming, who_from, source, 0, delay, builtin_type, event_num_to_cancel ); } } // just send them a pong back as fast as possible void process_ping_packet(ubyte * /*data*/, header *hinfo) { net_addr addr; int offset; offset = HEADER_LENGTH; PACKET_SET_SIZE(); // get the address to return the pong to fill_net_addr(&addr, hinfo->addr, hinfo->port); // send the pong send_pong(&addr); } // right now it just routes the pong through to the standalone gui, which is the only // system which uses ping and pong right now. void process_pong_packet(ubyte * /*data*/, header *hinfo) { net_player *p; net_addr addr; int offset,lookup; offset = HEADER_LENGTH; fill_net_addr(&addr, hinfo->addr, hinfo->port); PACKET_SET_SIZE(); // if we're connected , see who sent us this pong if(Net_player->flags & NETINFO_FLAG_CONNECTED){ lookup = find_player_index(hinfo->id); if(lookup == -1){ return; } p = &Net_players[lookup]; // evaluate the ping multi_ping_eval_pong(&Net_players[lookup].s_info.ping, timer_get_fixed_seconds()); // put in calls to any functions which may want to know about the ping times from // this guy if (Game_mode & GM_STANDALONE_SERVER) { std_update_player_ping(p); } // mark his socket as still alive (extra precaution) psnet_mark_received(Net_players[lookup].reliable_socket); } // otherwise, do any special processing else { // if we're in the join game state, see if this pong came from a server on our // list if(gameseq_get_state() == GS_STATE_MULTI_JOIN_GAME){ multi_join_eval_pong(&addr, timer_get_fixed_seconds()); } } } // send a ping packet void send_ping(net_addr *addr) { unsigned char data[8]; int packet_size; // build the header and send the packet BUILD_HEADER( PING ); psnet_send(addr, &data[0], packet_size); } // send a pong packet void send_pong(net_addr *addr) { unsigned char data[8]; int packet_size; // build the header and send the packet BUILD_HEADER(PONG); psnet_send(addr, &data[0], packet_size); } // sent from host to master. give me the list of missions you have. // this will be used only in a standalone mode void send_mission_list_request( int /*what*/ ) { ubyte data[MAX_PACKET_SIZE]; int packet_size; // build the header and ask for a list of missions or campaigns (depending // on the 'what' flag). BUILD_HEADER(MISSION_REQUEST); multi_io_send_reliable(Net_player, data, packet_size); } // maximum number of bytes that we can send in a mission items packet. #define MAX_MISSION_ITEMS_BYTES (MAX_PACKET_SIZE - (sizeof(multi_create_info) + 1) ) // defines used to tell what type of packets are being sent #define MISSION_LIST_ITEMS 1 #define CAMPAIGN_LIST_ITEMS 2 // send an individual mission file item void send_mission_items( net_player *pl ) { ubyte data[MAX_PACKET_SIZE]; int packet_size, i; ubyte stop, type; // build the header BUILD_HEADER(MISSION_ITEM); // send the list of missions and campaigns avilable on the server. Stop when // reaching a certain maximum type = MISSION_LIST_ITEMS; ADD_DATA( type ); for (i = 0; i < (int)Multi_create_mission_list.size(); i++ ) { stop = 0; ADD_DATA( stop ); ADD_STRING( Multi_create_mission_list[i].filename ); ADD_STRING( Multi_create_mission_list[i].name ); ADD_INT( Multi_create_mission_list[i].flags ); ADD_DATA( Multi_create_mission_list[i].max_players ); ADD_INT( Multi_create_mission_list[i].respawn ); // STANDALONE_ONLY ADD_DATA( Multi_create_mission_list[i].valid_status ); if ( packet_size > (int)MAX_MISSION_ITEMS_BYTES ) { stop = 1; ADD_DATA( stop ); multi_io_send_reliable(pl, data, packet_size); BUILD_HEADER( MISSION_ITEM ); ADD_DATA( type ); } } stop = 1; ADD_DATA(stop); multi_io_send_reliable(pl, data, packet_size); // send the campaign information type = CAMPAIGN_LIST_ITEMS; BUILD_HEADER(MISSION_ITEM); ADD_DATA( type ); for (i = 0; i < (int)Multi_create_campaign_list.size(); i++ ) { stop = 0; ADD_DATA( stop ); ADD_STRING( Multi_create_campaign_list[i].filename ); ADD_STRING( Multi_create_campaign_list[i].name ); ADD_INT( Multi_create_campaign_list[i].flags ); ADD_DATA( Multi_create_campaign_list[i].max_players ); if ( packet_size > (int)MAX_MISSION_ITEMS_BYTES ) { stop = 1; ADD_DATA( stop ); multi_io_send_reliable(pl, data, packet_size); BUILD_HEADER( MISSION_ITEM ); ADD_DATA( type ); } } stop = 1; ADD_DATA(stop); multi_io_send_reliable(pl, data, packet_size); } // process a request for a list of missions void process_mission_request_packet(ubyte * /*data*/, header *hinfo) { int player_num,offset; offset = HEADER_LENGTH; PACKET_SET_SIZE(); // fill in the address information of where this came from player_num = find_player_index(hinfo->id); if(player_num == -1){ nprintf(("Network","Could not find player to send mission list items to!\n")); return; } send_mission_items( &Net_players[player_num] ); } // process an individual mission file item void process_mission_item_packet(ubyte *data,header *hinfo) { int offset, flags; char filename[MAX_FILENAME_LEN], name[NAME_LENGTH], valid_status; ubyte stop, type,max_players; uint respawn; multi_create_info mcip; Assert(gameseq_get_state() == GS_STATE_MULTI_HOST_SETUP); offset = HEADER_LENGTH; GET_DATA( type ); GET_DATA(stop); while( !stop ) { GET_STRING( filename ); GET_STRING( name ); GET_INT( flags ); GET_DATA( max_players ); // missions also have respawns and a crc32 associated with them if(type == MISSION_LIST_ITEMS){ GET_UINT(respawn); // STANDALONE_ONLY GET_DATA(valid_status); strcpy_s(mcip.filename, filename ); strcpy_s(mcip.name, name ); mcip.flags = flags; mcip.respawn = respawn; mcip.max_players = max_players; // STANDALONE_ONLY mcip.valid_status = valid_status; Multi_create_mission_list.push_back( mcip ); } else if ( type == CAMPAIGN_LIST_ITEMS ) { strcpy_s(mcip.filename, filename ); strcpy_s(mcip.name, name ); mcip.flags = flags; mcip.respawn = 0; mcip.max_players = max_players; Multi_create_campaign_list.push_back( mcip ); } GET_DATA( stop ); } PACKET_SET_SIZE(); // this will cause whatever list to get resorted (although they should be appearing in order) multi_create_setup_list_data(-1); } // send a request to the server to pause or unpause the game void send_multi_pause_packet(int pause) { ubyte data[MAX_PACKET_SIZE]; ubyte val; int packet_size = 0; Assert(!(Net_player->flags & NETINFO_FLAG_AM_MASTER)); // build the header BUILD_HEADER(MULTI_PAUSE_REQUEST); val = (ubyte) pause; // add the pause info ADD_DATA(val); // send the request to the server multi_io_send_reliable(Net_player, data, packet_size); } // process a pause update packet (pause, unpause, etc) void process_multi_pause_packet(ubyte *data, header *hinfo) { int offset; ubyte val; int player_index; offset = HEADER_LENGTH; // get the data GET_DATA(val); PACKET_SET_SIZE(); // get who sent the packet player_index = find_player_index(hinfo->id); // if we don't know who sent the packet, don't do anything if(player_index == -1){ return; } // if we're the server, we should evaluate whether this guy is allowed to send the packet multi_pause_server_eval_request(&Net_players[player_index],(int)val); } // send a game information update void send_game_info_packet() { int packet_size; ubyte data[MAX_PACKET_SIZE], paused; // set the paused variable paused = (ubyte)((Netgame.game_state == NETGAME_STATE_PAUSED)?1:0); BUILD_HEADER(GAME_INFO); ADD_INT( Missiontime ); // NOTE: this is a long so careful with swapping in 64-bit platforms - taylor ADD_DATA( paused ); multi_io_send_to_all(data, packet_size); } // process a game information update void process_game_info_packet( ubyte *data, header *hinfo ) { int offset; fix mission_time; ubyte paused; offset = HEADER_LENGTH; // get the mission time -- we should examine our time and the time from the server. If off by some delta // time, set our time to server time (should take ping time into account!!!) GET_INT( mission_time ); // NOTE: this is a long so careful with swapping in 64-bit platforms - taylor GET_DATA( paused ); PACKET_SET_SIZE(); } // send an ingame nak packet void send_ingame_nak(int state, net_player *p) { ubyte data[MAX_PACKET_SIZE]; int packet_size; packet_size = 0; BUILD_HEADER(INGAME_NAK); ADD_INT(state); multi_io_send_reliable(p, data, packet_size); } // process an ingame nak packet void process_ingame_nak(ubyte *data, header *hinfo) { int offset,state,pid; offset = HEADER_LENGTH; GET_INT(state); PACKET_SET_SIZE(); pid = find_player_index(hinfo->id); if(pid < 0){ return; } switch(state){ case ACK_FILE_ACCEPTED : Assert(Net_player->flags & NETINFO_FLAG_INGAME_JOIN); nprintf(("Network","Mission file rejected by server, aborting...\n")); multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_FILE_REJECTED); break; } } // process a packet indicating that we should jump straight to the debrief screen void process_force_end_mission_packet(ubyte * /*data*/, header *hinfo) { int offset; offset = HEADER_LENGTH; PACKET_SET_SIZE(); // TODO: Obsolete packet - Remove on next multi bump // // This method of ending a mission was horribly broken and skipped over a // lot of necessary state changes resulting in broken standalone net traffic // // We need to support receiving this packet for compatibility sake, but it // should be removed on the next multi bump (as noted in #6927) } // send a packet telling players to end the mission void send_endgame_packet(net_player *pl) { ubyte data[MAX_PACKET_SIZE]; int packet_size; packet_size = 0; BUILD_HEADER(MISSION_END); // sending to a specific player? if(pl != NULL){ Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER); multi_io_send_reliable(pl, data, packet_size); return; } if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ // send all player stats here multi_broadcast_stats(STATS_MISSION); // if in dogfight mode, send all dogfight stats as well ml_string("Before dogfight stats!"); if(Netgame.type_flags & NG_TYPE_DOGFIGHT){ ml_string("Sending dogfight stats!"); multi_broadcast_stats(STATS_DOGFIGHT_KILLS); } ml_string("After dogfight stats!"); // tell everyone to leave the game multi_io_send_to_all_reliable(data, packet_size); } else { multi_io_send_reliable(Net_player, data, packet_size); } } // process a packet indicating we should end the current mission void process_endgame_packet(ubyte * /*data*/, header *hinfo) { int offset; int player_num; offset = HEADER_LENGTH; PACKET_SET_SIZE(); ml_string("Receiving endgame packet"); // if I'm the server, I should evaluate whether the sender is authorized to end the game if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ // determine who this came from and make sure he is allowed to end the game player_num = find_player_index(hinfo->id); Assert(player_num != -1); if(player_num < 0){ return; } // if the player is allowed to end the mission if(!multi_can_end_mission(&Net_players[player_num])){ return; } // act as if we hit alt+j locally multi_handle_end_mission_request(); } // all clients process immediately else { // ingame joiners should quit when they receive an endgame packet since the game is over if(Net_player->flags & NETINFO_FLAG_INGAME_JOIN){ multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_EARLY_END); return; } // do any special processing for being in a state other than the gameplay state multi_handle_state_special(); // If end-mission fired via SEXP, enter debrief immediately (skipping warp-out). // Stats have already been received via multi_broadcast_stats() which send_endgame_packet() // calls before sending this packet, so scoring_level_close() will see correct kill data. if (Multi_sexp_end_mission_pending) { Multi_sexp_end_mission_pending = false; send_debrief_event(); } // make sure we're not already in the debrief state if((gameseq_get_state() != GS_STATE_DEBRIEF) && (gameseq_get_state() != GS_STATE_MULTI_DOGFIGHT_DEBRIEF)){ multi_warpout_all_players(); } } } // send a position/orientation update for myself (if I'm an observer) void send_observer_update_packet() { ubyte data[MAX_PACKET_SIZE]; int packet_size; int ret; ushort target_sig; // its possible for the master to be an observer if has run out of respawns. In this case, he doesn't need // to send any update packets to anyone. if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ return; } if((Player_obj == NULL) || (Player_obj->type != OBJ_OBSERVER) || (Net_player == NULL) || !(Net_player->flags & NETINFO_FLAG_OBSERVER)){ return; } packet_size = 0; BUILD_HEADER(OBSERVER_UPDATE); ret = multi_pack_unpack_position( 1, data + packet_size, &Player_obj->pos ); packet_size += ret; angles temp_angles; vm_extract_angles_matrix_alternate(&temp_angles, &Player_obj->orient); ret = multi_pack_unpack_orient( 1, data + packet_size, &temp_angles); packet_size += ret; // add targeting infomation if((Player_ai != NULL) && (Player_ai->target_objnum >= 0)){ target_sig = Objects[Player_ai->target_objnum].net_signature; } else { target_sig = 0; } ADD_USHORT(target_sig); multi_io_send(Net_player, data, packet_size); } // process a position/orientation update from an observer void process_observer_update_packet(ubyte *data, header *hinfo) { int offset,ret; int obs_num; vec3d g_vec; angles temp_angles; matrix g_mat; physics_info bogus_pi; ushort target_sig; object *target_obj; offset = HEADER_LENGTH; obs_num = find_player_index(hinfo->id); memset(&bogus_pi,0,sizeof(physics_info)); ret = multi_pack_unpack_position( 0, data + offset, &g_vec ); offset += ret; ret = multi_pack_unpack_orient( 0, data + offset, &temp_angles ); vm_angles_2_matrix(&g_mat, &temp_angles); offset += ret; // targeting information GET_USHORT(target_sig); PACKET_SET_SIZE(); if((obs_num < 0) || (Net_players[obs_num].m_player->objnum < 0)){ return; } // set targeting info if(target_sig == 0){ Net_players[obs_num].s_info.target_objnum = -1; } else { target_obj = multi_get_network_object(target_sig); Net_players[obs_num].s_info.target_objnum = (target_obj == NULL) ? -1 : OBJ_INDEX(target_obj); } Objects[Net_players[obs_num].m_player->objnum].pos = g_vec; Objects[Net_players[obs_num].m_player->objnum].orient = g_mat; Net_players[obs_num].s_info.eye_pos = g_vec; Net_players[obs_num].s_info.eye_orient = g_mat; } void send_netplayer_slot_packet() { ubyte data[MAX_PACKET_SIZE]; int packet_size,idx; ubyte stop; char val; packet_size = 0; stop = 0xff; BUILD_HEADER(NETPLAYER_SLOTS_P); for(idx=0;idxobjnum].net_signature); ADD_SHORT(static_cast(Net_players[idx].p_info.ship_class)); val = static_cast(Net_players[idx].p_info.ship_index); ADD_DATA(val); } } stop = 0x0; ADD_DATA(stop); // standalone case or not if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ multi_io_send_to_all_reliable(data, packet_size); } else { multi_io_send_reliable(Net_player, data, packet_size); } } void process_netplayer_slot_packet(ubyte *data, header *hinfo) { int offset; int player_num; char ship_index; ushort net_sig; object *objp; ubyte stop; short player_id, ship_class; offset = HEADER_LENGTH; // first untag all of the player ships and make them OF_COULD_BE_PLAYER multi_untag_player_ships(); GET_DATA(stop); while(stop != 0x0){ GET_SHORT(player_id); GET_USHORT(net_sig); GET_SHORT(ship_class); GET_DATA(ship_index); player_num = find_player_index(player_id); if(player_num < 0){ nprintf(("Network","Error looking up player for object/slot assignment!!\n")); } else { // call the function in multiutil.cpp to set up the player object stuff // being careful not to muck with the standalone object if(!((player_num == 0) && (Game_mode & GM_STANDALONE_SERVER))){ objp = multi_get_network_object(net_sig); if (objp == NULL) { Error(LOCATION, "Could not retrieve net object for signature %d!\n", net_sig); } multi_assign_player_ship( player_num, objp, ship_class ); Net_players[player_num].p_info.ship_index = ship_index; objp->flags.remove(Object::Object_Flags::Could_be_player); objp->flags.set(Object::Object_Flags::Player_ship); } } GET_DATA(stop); } PACKET_SET_SIZE(); // standalone should forward the packet and wait for a response if(Game_mode & GM_STANDALONE_SERVER){ send_netplayer_slot_packet(); } Net_player->state = NETPLAYER_STATE_SLOT_ACK; send_netplayer_update_packet(); } // two functions to deal with ships changing their primary/secondary weapon status. 'what' indicates // if this change is a primary or secondary change. new_bank is the new current primary/secondary // bank, link_status is whether primaries are linked or not, or secondaries are dual fire or not void send_ship_weapon_change( ship *shipp, int what, int new_bank, int link_status ) { ubyte data[MAX_PACKET_SIZE], utmp; int packet_size; BUILD_HEADER(SHIP_WSTATE_CHANGE); ADD_USHORT( Objects[shipp->objnum].net_signature ); utmp = (ubyte)(what); ADD_DATA( utmp ); utmp = (ubyte)(new_bank); ADD_DATA( utmp ); utmp = (ubyte)(link_status); ADD_DATA( utmp ); // Removed the above psnet_send() call - it didn't appear to do anything since it was called only from the server anyway - DB multi_io_send_to_all_reliable(data, packet_size); } void process_ship_weapon_change( ubyte *data, header *hinfo ) { int offset; ushort signature; ubyte what, new_bank, link_status; object *objp; ship *shipp; offset = HEADER_LENGTH; GET_USHORT( signature ); GET_DATA( what ); GET_DATA( new_bank ); GET_DATA( link_status ); PACKET_SET_SIZE(); objp = multi_get_network_object( signature ); if ( objp == NULL ) { nprintf(("network", "Unable to locate ship with signature %d for weapon state change\n", signature)); return; } // Assert( objp->type == OBJ_SHIP ); if(objp->type != OBJ_SHIP){ return; } // if this is my data, do nothing since I already have my own data if ( objp == Player_obj ){ return; } // now, get the ship and set the new bank and link modes based on the 'what' value shipp = &Ships[objp->instance]; if ( what == MULTI_PRIMARY_CHANGED ) { shipp->weapons.current_primary_bank = new_bank; shipp->flags.set(Ship::Ship_Flags::Primary_linked, link_status != 0); } else { shipp->weapons.current_secondary_bank = new_bank; shipp->flags.set(Ship::Ship_Flags::Secondary_dual_fire, link_status != 0); } } // ship status change procedure // 1.) - Client runs through the normal button_function procedure. Any remaining control bits are implied as being // server critical. // 2.) - Client puts this button_info item into his last_buttons array and sends a bunch of SHIP_STATUS packets // for added redundancy. // 3.) - Receives the packet. Checks to see if the net_player on his side already has this one defined. If so, it // ignores as a repeat packet. Otherwise it puts it in the last_buttons array for the net_player // 4.) - Server applies the command on his side (with multi_apply_ship_status(...) and sends the ack (also a SHIP_STATUS) // back to the client. Also sends multiple times for redundancy // 5.) - Receives the packet back. Does a lookup into his last_buttons array. If he finds the match, apply the functions // and remove the item from the list. If no match is found it means that either he has received an ack, has acted // on it and removed it, or that it has been "timed out" and replaced by a newer button_info. #define SHIP_STATUS_REPEAT 2 void send_ship_status_packet(net_player *pl, button_info *bi, int id) { int idx, temp; ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; uint8_t num_fields = 0; if(pl == NULL){ return; } BUILD_HEADER(SHIP_STATUS_CHANGE); ADD_INT(id); for (num_fields = NUM_BUTTON_FIELDS; num_fields > 0; num_fields--) { if (bi->status[num_fields-1]) { break; } } ADD_DATA(num_fields); for (idx = 0; idx < num_fields; idx++) { temp = bi->status[idx]; ADD_INT(temp); } // server should send reliably (response packet) if(MULTIPLAYER_MASTER){ multi_io_send_reliable(pl, data, packet_size); } else { multi_io_send(pl, data, packet_size); } } void process_ship_status_packet(ubyte *data, header *hinfo) { int idx; int offset; int player_num,unique_id; button_info bi; int i_tmp; uint8_t num_fields = 0; offset = HEADER_LENGTH; // zero out the button info structure for good measure memset(&bi,0,sizeof(button_info)); // read the button-info GET_INT(unique_id); GET_DATA(num_fields); for (idx = 0; idx < num_fields; idx++) { GET_INT(i_tmp); if (idx < NUM_BUTTON_FIELDS) { bi.status[idx] = i_tmp; } } PACKET_SET_SIZE(); // this will be handled differently client and server side. Duh. if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ // SERVER SIDE // find which net-player has sent us butotn information player_num = find_player_index(hinfo->id); Assert(player_num >= 0); if(player_num < 0){ return; } // don't process critical button information for observers // its a new button_info for this guy. apply and ack if(!MULTI_OBSERVER(Net_players[player_num]) && !lookup_ship_status(&Net_players[player_num],unique_id)){ // mark that he's pressed this button // add_net_button_info(&Net_players[player_num], &bi, unique_id); // send a return packet send_ship_status_packet(&Net_players[player_num], &bi,unique_id); // apply the button presses to his ship as normal multi_apply_ship_status(&Net_players[player_num], &bi, 0); } // else ignore it as a repeat from the same guy } else { // CLIENT SIDE // this is the return from the server, so we should now apply them locally // if(lookup_ship_status(Net_player,unique_id,1)){ multi_apply_ship_status(Net_player, &bi, 1); // } } } // MWA 4/28/9 -- redid this function since message all fighers was really broken // for clients. Left all details to this function instead of higher level messaging // code void send_player_order_packet(int type, int index, int cmd) { ubyte data[MAX_PACKET_SIZE]; ubyte val; ushort target_net_signature; int s_index = -1; int packet_size = 0; BUILD_HEADER(PLAYER_ORDER_PACKET); val = (ubyte)type; ADD_DATA(val); // ship order or wing order, or message all fighters // if we are not messaging all ships or wings, add the index, which is the shipnum or wingnum if ( val != SQUAD_MSG_ALL ){ ADD_INT(index); // net signature of target ship } ADD_INT(cmd); // the command itself // add target data. target_net_signature = 0; if ( Player_ai->target_objnum != -1 ){ target_net_signature = Objects[Player_ai->target_objnum].net_signature; } ADD_USHORT( target_net_signature ); if ( (Player_ai->target_objnum != -1) && (Player_ai->targeted_subsys != NULL) ) { s_index = ship_get_subsys_index( Player_ai->targeted_subsys ); } ADD_SHORT(static_cast(s_index)); multi_io_send_reliable(Net_player, data, packet_size); } // brief explanation : // in either case (wing or ship command), we need to send in a pseudo-ai object. Basically, both command handler // functions "normally" (non multiplayer) use a couple of the Player_ai fields. So, we just fill in the ones necessary // (which we can reconstruct from the packet data), and pass this as the default variable ai_info *local // Its kind of a hack, but it eliminates the need to go in and screw around with quite a bit of code void process_player_order_packet(ubyte *data, header *hinfo) { int offset, player_num, command, index = 0, tobjnum_save; ushort target_net_signature; char type; short t_subsys; object *objp, *target_objp; ai_info *aip; ship *shipp; ship_subsys *tsubsys_save, *targeted_subsys; Assert(MULTIPLAYER_MASTER); // packet values - its easier to read all of these in first offset = HEADER_LENGTH; GET_DATA( type ); if ( type != SQUAD_MSG_ALL ){ GET_INT( index ); } GET_INT( command ); GET_USHORT( target_net_signature ); GET_SHORT( t_subsys ); PACKET_SET_SIZE(); player_num = find_player_index(hinfo->id); if(player_num == -1){ nprintf(("Network","Received player order packet from unknown player\n")); return; } objp = &Objects[Net_players[player_num].m_player->objnum]; if ( objp->type != OBJ_SHIP ) { nprintf(("Network", "not doing player order because object requestting is not a ship\n")); return; } // HACK HACK HACK HACK HACK HACK // if the player has sent a rearm-repair me message, we should bail here after evaluating it, since most likely the rest of // the data is BOGUS. All people should be able to to these things as well. if(command == REARM_REPAIR_ME_ITEM){ hud_squadmsg_repair_rearm(0,&Objects[Net_players[player_num].m_player->objnum]); return; } else if(command == ABORT_REARM_REPAIR_ITEM){ hud_squadmsg_repair_rearm_abort(0,&Objects[Net_players[player_num].m_player->objnum]); return; } // if this player is not allowed to do messaging, quit here if( !multi_can_message(&Net_players[player_num]) ){ nprintf(("Network","Received player order packet from player not allowed to give orders!!\n")); return; } // check to see if the type of order is a reinforcement call. If so, intercept it, and // then call them in. if ( type == SQUAD_MSG_REINFORCEMENT ) { Assert(Reinforcements.in_bounds(index)); hud_squadmsg_call_reinforcement(Reinforcements[index], player_num); return; } // set the player's ai information here shipp = &Ships[objp->instance]; aip = &Ai_info[shipp->ai_index]; // get the target objnum and targeted subsystem. Quick out if we don't have an object to act on. target_objp = multi_get_network_object( target_net_signature ); if ( target_objp == NULL ) { return; } targeted_subsys = NULL; if ( t_subsys != -1 ) { Assert( target_objp != NULL ); targeted_subsys = ship_get_indexed_subsys( &Ships[target_objp->instance], t_subsys); } // save and restore the target objnum and targeted subsystem so that we don't mess up other things // here tobjnum_save = aip->target_objnum; tsubsys_save = aip->targeted_subsys; if ( target_objp ) { aip->target_objnum = OBJ_INDEX(target_objp); } else { aip->target_objnum = -1; } aip->targeted_subsys = targeted_subsys; if ( type == SQUAD_MSG_SHIP ) { hud_squadmsg_send_ship_command(index, command, 1, SQUADMSG_HISTORY_ADD_ENTRY, player_num); } else if ( type == SQUAD_MSG_WING ) { hud_squadmsg_send_wing_command(index, command, 1, SQUADMSG_HISTORY_ADD_ENTRY, player_num); } else if ( type == SQUAD_MSG_ALL ) { hud_squadmsg_send_to_all_fighters( command, player_num ); } Assert(tobjnum_save != Ships[aip->shipnum].objnum); // make sure not targeting self aip->target_objnum = tobjnum_save; aip->targeted_subsys = tsubsys_save; } // FILE SIGNATURE stuff : // there are 2 cases for file signature sending which are handled very differently // 1.) Pregame. In this case, the host requires that all clients send a filesig packet (when process_file_sig() is called, it // posts an ACK_FILE_ACCEPTED packet to ack_evaluate, so he thinks they have acked). // 2.) Ingame join. In this case, the client sends his filesig packet automatically to the server and the _client_ waits for // the ack, before continuing to join. It would be way too messy to have the server wait on the clients ack, since he // would have to keep track of up to potentially 14 other ack handles (ouch). void send_file_sig_packet(ushort sum_sig,int length_sig) { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; BUILD_HEADER(FILE_SIG_INFO); ADD_USHORT(sum_sig); ADD_INT(length_sig); multi_io_send_reliable(Net_player, data, packet_size); } void process_file_sig_packet(ubyte *data, header *hinfo) { int offset; int length_sig; ushort sum_sig; offset = HEADER_LENGTH; // should only be received on the server-side Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER); GET_USHORT(sum_sig); GET_INT(length_sig); PACKET_SET_SIZE(); server_verify_filesig(hinfo->id, sum_sig, length_sig); } void send_file_sig_request(char *file_name) { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; BUILD_HEADER(FILE_SIG_REQUEST); ADD_STRING(file_name); Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER); multi_io_send_to_all_reliable(data, packet_size); } void process_file_sig_request(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; // get the mission name GET_STRING(Netgame.mission_name); PACKET_SET_SIZE(); // set the current mission filename strcpy_s(Game_current_mission_filename,Netgame.mission_name); // get the checksum multi_get_mission_checksum(Game_current_mission_filename); if(!multi_endgame_ending()){ // reply to the server send_file_sig_packet(Multi_current_file_checksum,Multi_current_file_length); } } // functions to deal with subsystems getting whacked void send_subsystem_destroyed_packet( ship *shipp, int index, vec3d world_hitpos ) { ubyte data[MAX_PACKET_SIZE]; int packet_size; ubyte uindex; vec3d tmp, local_hitpos; object *objp; Assert ( index < UCHAR_MAX ); uindex = (ubyte)(index); objp = &Objects[shipp->objnum]; vm_vec_sub(&tmp, &world_hitpos, &objp->pos ); vm_vec_rotate( &local_hitpos, &tmp, &objp->orient ); BUILD_HEADER(SUBSYSTEM_DESTROYED); ADD_USHORT( Objects[shipp->objnum].net_signature ); ADD_DATA( uindex ); ADD_VECTOR( local_hitpos ); multi_io_send_to_all_reliable(data, packet_size); } void process_subsystem_destroyed_packet( ubyte *data, header *hinfo ) { int offset; ushort signature; ubyte uindex; object *objp; vec3d local_hit_pos, world_hit_pos; offset = HEADER_LENGTH; GET_USHORT( signature ); GET_DATA( uindex ); GET_VECTOR( local_hit_pos ); // get the network object. process it if we find it. objp = multi_get_network_object( signature ); if ( objp != NULL ) { ship *shipp; ship_subsys *subsysp; // be sure we have a ship!!! // Assert ( objp->type == OBJ_SHIP ); if(objp->type != OBJ_SHIP){ PACKET_SET_SIZE(); return; } shipp = &Ships[objp->instance]; // call to get the pointer to the subsystem we should be working on subsysp = ship_get_indexed_subsys( shipp, (int)uindex ); if (subsysp == NULL) { nprintf(("Network", "Could not find subsys %d for ship %s to process as being destroyed\n", (int)uindex, shipp->ship_name )); PACKET_SET_SIZE(); return; } // Cyborg17 - We need to do this here because otherwise FSO will not mark subsystem strength as zero (It used to rely on the object Update packet) subsysp->current_hits = 0; vm_vec_unrotate( &world_hit_pos, &local_hit_pos, &objp->orient ); vm_vec_add2( &world_hit_pos, &objp->pos ); do_subobj_destroyed_stuff( shipp, subsysp, &world_hit_pos ); if ( objp == Player_obj ) { hud_gauge_popup_start(HUD_DAMAGE_GAUGE, 5000); } } PACKET_SET_SIZE(); } // packet to tell clients cargo of a ship was revealed to all void send_subsystem_cargo_revealed_packet( ship *shipp, int index ) { ubyte data[MAX_PACKET_SIZE], uindex; int packet_size; Assert ( index < UCHAR_MAX ); uindex = (ubyte)(index); // build the header and add the data BUILD_HEADER(SUBSYS_CARGO_REVEALED); ADD_USHORT( Objects[shipp->objnum].net_signature ); ADD_DATA( uindex ); // server sends to all players if(MULTIPLAYER_MASTER){ multi_io_send_to_all_reliable(data, packet_size); } // clients just send to the server else { multi_io_send_reliable(Net_player, data, packet_size); } } // process a subsystem cargo revealed packet void process_subsystem_cargo_revealed_packet( ubyte *data, header *hinfo ) { int offset; ushort signature; ubyte uindex; object *objp; ship *shipp; ship_subsys *subsysp; offset = HEADER_LENGTH; GET_USHORT( signature ); GET_DATA( uindex ); PACKET_SET_SIZE(); // get a ship pointer and call the ship function to reveal the cargo objp = multi_get_network_object( signature ); if ( objp == NULL ) { nprintf(("Network", "Could not find object with net signature %d for cargo revealed\n", signature )); return; } // Assert( objp->type == OBJ_SHIP ); if((objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS)){ return; } shipp = &Ships[objp->instance]; // call to get the pointer to the subsystem we should be working on subsysp = ship_get_indexed_subsys( shipp, (int)uindex ); if (subsysp == NULL) { nprintf(("Network", "Could not find subsys for ship %s for cargo revealed\n", Ships[objp->instance].ship_name )); return; } // this will take care of re-routing to all other clients ship_do_cap_subsys_cargo_revealed( shipp, subsysp, 1 ); // server should rebroadcast if(MULTIPLAYER_MASTER){ send_subsystem_cargo_revealed_packet(&Ships[objp->instance], (int)uindex); } } // packet to tell clients cargo of a ship was hidden to all void send_subsystem_cargo_hidden_packet( ship *shipp, int index ) { ubyte data[MAX_PACKET_SIZE], uindex; int packet_size; Assert ( index < UCHAR_MAX ); uindex = (ubyte)(index); // build the header and add the data BUILD_HEADER(SUBSYS_CARGO_HIDDEN); ADD_USHORT( Objects[shipp->objnum].net_signature ); ADD_DATA( uindex ); // server sends to all players if(MULTIPLAYER_MASTER){ multi_io_send_to_all_reliable(data, packet_size); } // clients just send to the server else { multi_io_send_reliable(Net_player, data, packet_size); } } // process a subsystem cargo hidden packet void process_subsystem_cargo_hidden_packet( ubyte *data, header *hinfo ) { int offset; ushort signature; ubyte uindex; object *objp; ship *shipp; ship_subsys *subsysp; offset = HEADER_LENGTH; GET_USHORT( signature ); GET_DATA( uindex ); PACKET_SET_SIZE(); // get a ship pointer and call the ship function to reveal the cargo objp = multi_get_network_object( signature ); if ( objp == NULL ) { nprintf(("Network", "Could not find object with net signature %d for cargo hidden\n", signature )); return; } // Assert( objp->type == OBJ_SHIP ); if((objp->type != OBJ_SHIP) || (objp->instance < 0) || (objp->instance >= MAX_SHIPS)){ return; } shipp = &Ships[objp->instance]; // call to get the pointer to the subsystem we should be working on subsysp = ship_get_indexed_subsys( shipp, (int)uindex ); if (subsysp == NULL) { nprintf(("Network", "Could not find subsys for ship %s for cargo hidden\n", Ships[objp->instance].ship_name )); return; } // this will take care of re-routing to all other clients ship_do_cap_subsys_cargo_hidden( shipp, subsysp, 1 ); // server should rebroadcast if(MULTIPLAYER_MASTER){ send_subsystem_cargo_hidden_packet(&Ships[objp->instance], (int)uindex); } } void send_netplayer_load_packet(net_player *pl) { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; BUILD_HEADER(LOAD_MISSION_NOW); ADD_STRING(Netgame.mission_name); if(pl == NULL){ multi_io_send_to_all_reliable(data, packet_size); } else { multi_io_send_reliable(pl, data, packet_size); } } void process_netplayer_load_packet(ubyte *data, header *hinfo) { char str[100]; int offset = HEADER_LENGTH; GET_STRING(str); PACKET_SET_SIZE(); strcpy_s(Netgame.mission_name,str); strcpy_s(Game_current_mission_filename,str); if(!Multi_mission_loaded){ // MWA 2/3/98 -- ingame join changes!!! // everyone can go through the same mission loading path here!!!! nprintf(("Network","Loading mission...\n")); // notify everyone that I'm loading the mission Net_player->state = NETPLAYER_STATE_MISSION_LOADING; send_netplayer_update_packet(); // do the load itself game_start_mission(); // ingame joiners need to "untag" all player ships as could_be_players. The ingame joining // code will remark the correct player ships if ( Net_player->flags & NETINFO_FLAG_INGAME_JOIN ) { multi_untag_player_ships(); } Net_player->flags |= NETINFO_FLAG_MISSION_OK; Net_player->state = NETPLAYER_STATE_MISSION_LOADED; send_netplayer_update_packet(); Multi_mission_loaded = 1; nprintf(("Network","Finished loading mission\n")); } } void send_jump_into_mission_packet(net_player *pl) { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER); BUILD_HEADER(JUMP_INTO_GAME); // ingame joiners will get special data. We need to tell them about the state of the mission, like paused, // and possible other things. if ( pl != NULL ) { if ( pl->flags & NETINFO_FLAG_INGAME_JOIN ) { ADD_INT(Netgame.game_state); } } // broadcast if(pl == NULL){ multi_io_send_to_all_reliable(data, packet_size); } // send to a specific player else { multi_io_send_reliable(pl, data, packet_size); } } void process_jump_into_mission_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; int state; state = 0; // if I am ingame joining, there should be extra data. For now, this data is the netgame state. // the game could be paused, so ingame joiner needs to deal with it. if ( Net_player->flags & NETINFO_FLAG_INGAME_JOIN ) { GET_INT( state ); Netgame.game_state = state; } PACKET_SET_SIZE(); // handle any special processing for being in a weird substate multi_handle_state_special(); // if I'm an ingame joiner, go to the ship select screen, or if I'm an observer, jump right in! if(Net_player->flags & NETINFO_FLAG_INGAME_JOIN){ if(Net_player->flags & NETINFO_FLAG_OBSERVER){ multi_ingame_observer_finish(); } else { gameseq_post_event(GS_EVENT_INGAME_PRE_JOIN); Net_player->state = NETPLAYER_STATE_INGAME_SHIP_SELECT; send_netplayer_update_packet(); } } else { // start the mission!! if(!(Game_mode & GM_IN_MISSION) && !(Game_mode & GM_STANDALONE_SERVER)){ Netgame.game_state = NETGAME_STATE_IN_MISSION; gameseq_post_event(GS_EVENT_ENTER_GAME); Net_player->state = NETPLAYER_STATE_IN_MISSION; send_netplayer_update_packet(); } } // recalc all object pairs now extern void obj_reset_all_collisions(); obj_reset_all_collisions(); // display some cool text multi_common_add_text(XSTR("Received mission start\n",720),1); // NETLOG ml_string(NOX("Client received mission start from server - entering mission")); } //XSTR:OFF const char *repair_text[] = { "unknown", "REPAIR_INFO_BEGIN", "REPAIR_INFO_END", "REPAIR_INFO_UPDATE", "REPAIR_INFO_QUEUE", "REPAIR_INFO_ABORT", "REPAIR_INFO_BROKEN", "REPAIR_INFO_WARP_ADD", "REPAIR_INFO_WARP_REMOVE", "REPAIR_INFO_ONWAY", "REPAIR_INFO_KILLED", "REPAIR_INFO_COMPLETE", }; //XSTR:ON // the following two routines deal with updating and sending information regarding players // rearming and repairing during the game. The process function calls the routines to deal with // setting flags and other interesting things. void send_repair_info_packet(object *repaired_objp, object *repair_objp, int code ) { int packet_size = 0; ushort repaired_signature, repair_signature; ubyte data[MAX_PACKET_SIZE]; ubyte cd; // use the network signature of the destination object if there is one, -1 otherwise. // client will piece it all together repaired_signature = repaired_objp->net_signature; // the repair ship may be NULL here since it might have been destroyed repair_signature = 0; if ( repair_objp ){ repair_signature = repair_objp->net_signature; } BUILD_HEADER(CLIENT_REPAIR_INFO); cd = (ubyte)code; ADD_DATA(cd); ADD_USHORT( repaired_signature ); ADD_USHORT( repair_signature ); multi_io_send_to_all_reliable(data, packet_size); nprintf(("Network", "Repair: %s sent to all players (%s/%s)\n", repair_text[cd], Ships[repaired_objp->instance].ship_name, (repair_objp==NULL)?"":Ships[repair_objp->instance].ship_name)); } void process_repair_info_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; ushort repaired_signature, repair_signature; object *repaired_objp, *repair_objp; ubyte code; GET_DATA(code); GET_USHORT( repaired_signature ); GET_USHORT( repair_signature ); PACKET_SET_SIZE(); repaired_objp = multi_get_network_object( repaired_signature ); repair_objp = multi_get_network_object( repair_signature ); nprintf(("Network", "Repair: %s received (%s/%s)\n", repair_text[code], (repaired_objp==NULL)?"":Ships[repaired_objp->instance].ship_name, (repair_objp==NULL)?"":Ships[repair_objp->instance].ship_name)); if ( Net_player->flags & NETINFO_FLAG_WARPING_OUT ){ return; } if ( repaired_objp == NULL ) { Int3(); // Sandeep says this is bad bad bad. No ship to repair. return; } // the hope is to simply call the routine in the ai code to set/unset flags // based on the code value and everything else should happen..I hope.... if ( (code != REPAIR_INFO_WARP_ADD) && (code != REPAIR_INFO_WARP_REMOVE ) ) { ai_do_objects_repairing_stuff( repaired_objp, repair_objp, (int)code ); // set the dock flags when repair begins. Prevents problem in lagging docking // packet. Also set any other flags/modes which need to be set to prevent Asserts. // bleah. if ( (code == REPAIR_INFO_BEGIN) && (repair_objp != NULL) ) { // Karajorma removed this in revision 4808 to fix bug 1088. Problem is, if // this was originally intended to prevent docking problems, will they return? /* // find indexes from goal ai_info *aip = &Ai_info[Ships[repair_objp->instance].ai_index]; Assert(aip->active_goal >= 0); ai_goal *aigp = &aip->goals[aip->active_goal]; Assert(aigp->flags[AI::Goal_Flags::Dockee_index_valid] && aigp->flags[AI::Goal_Flags::Docker_index_valid]); int docker_index = aigp->docker.index; int dockee_index = aigp->dockee.index; ai_do_objects_docked_stuff( repair_objp, docker_index, repaired_objp, dockee_index ); */ Ai_info[Ships[repair_objp->instance].ai_index].mode = AIM_DOCK; } // if the repair is done (either by abort, or ending), mark the repair ship's goal // as being done. if ( ((code == REPAIR_INFO_ABORT) || (code == REPAIR_INFO_END)) && repair_objp ){ ai_mission_goal_complete( &Ai_info[Ships[repair_objp->instance].ai_index] ); } } else { if ( code == REPAIR_INFO_WARP_ADD ){ mission_bring_in_support_ship( repaired_objp ); } else { mission_remove_scheduled_repair( repaired_objp ); } } } // sends information updating clients on certain AI information that clients will // need to know about to keep HUD information up to date. objp is the object that we // are updating, and what is the type of stuff that we are updating. void send_ai_info_update_packet( object *objp, char what, object * other_objp ) { int packet_size; ushort other_signature; ubyte data[MAX_PACKET_SIZE]; ai_info *aip; ai_goal *aigp = NULL; ubyte docker_index, dockee_index; // Assert( objp->type == OBJ_SHIP ); if(objp->type != OBJ_SHIP){ return; } aip = &Ai_info[Ships[objp->instance].ai_index]; // do an out here if ( Ships[objp->instance].is_dying_or_departing()) return; switch( what ) { case AI_UPDATE_DOCK: case AI_UPDATE_UNDOCK: Assert (other_objp != NULL); if (other_objp == NULL) { return; } break; default: Assert (other_objp == NULL); break; } BUILD_HEADER( AI_INFO_UPDATE ); ADD_USHORT( objp->net_signature ); ADD_DATA( what ); // depending on the "what" value, we will send different information // to the clients switch( what ) { case AI_UPDATE_DOCK: // for docking ships, add the signature of the ship that we are docked with. Assert( other_objp != NULL ); other_signature = other_objp->net_signature; // Goober5000 - this is sort of weird, but it's the best way to do it docker_index = (ubyte) dock_find_dockpoint_used_by_object(objp, other_objp); dockee_index = (ubyte) dock_find_dockpoint_used_by_object(other_objp, objp); ADD_USHORT( other_signature ); ADD_DATA( docker_index ); ADD_DATA( dockee_index ); break; case AI_UPDATE_UNDOCK: // same for undocking ships Assert( other_objp != NULL ); other_signature = other_objp->net_signature; ADD_USHORT( other_signature ); break; case AI_UPDATE_ORDERS: { int shipnum; // for orders, we only need to send a little bit of information here. Be sure that the // first order for this ship is active Assert( (aip->active_goal != AI_ACTIVE_GOAL_NONE) && (aip->active_goal != AI_ACTIVE_GOAL_DYNAMIC) ); aigp = &aip->goals[aip->active_goal]; ADD_INT( static_cast(aigp->ai_mode) ); ADD_INT( aigp->ai_submode ); shipnum = -1; if ( aigp->target_name != NULL ) shipnum = ship_name_lookup( aigp->target_name ); // the ship_name member of the goals structure may or may not contain a real shipname. If we don't // have a valid shipnum, then don't sweat it since it may not really be a ship. if ( shipnum != -1 ) { Assert( Ships[shipnum].objnum != -1 ); other_signature = Objects[Ships[shipnum].objnum].net_signature; } else other_signature = 0; ADD_USHORT( other_signature ); // for docking, add the dock and dockee index if (aigp->ai_mode == AI_GOAL_DOCK || aigp->ai_mode == AI_GOAL_REARM_REPAIR) { Assert(aigp->flags[AI::Goal_Flags::Dockee_index_valid] && aigp->flags[AI::Goal_Flags::Docker_index_valid]); Assert( (aigp->docker.index >= 0) && (aigp->docker.index < UCHAR_MAX) ); Assert( (aigp->dockee.index >= 0) && (aigp->dockee.index < UCHAR_MAX) ); docker_index = (ubyte) aigp->docker.index; dockee_index = (ubyte) aigp->dockee.index; ADD_DATA( docker_index ); ADD_DATA( dockee_index ); } break; } default: Int3(); } multi_rate_add(1, "aiu", packet_size); multi_io_send_to_all_reliable(data, packet_size); } // process an ai_info update packet. Docking/undocking, ai orders, etc. are taken care of here. This // information is mainly used to keep the clients HUD up to date with the appropriate information. void process_ai_info_update_packet( ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; ai_goal_mode mode; int int_mode, submode; ushort net_signature, other_net_signature; object *objp, *other_objp; ai_info *aip; ai_goal *aigp = NULL; char code; ubyte docker_index = 0, dockee_index = 0; GET_USHORT( net_signature ); // signature of the object that we are dealing with. GET_DATA( code ); // code of what we are doing. objp = multi_get_network_object( net_signature ); if ( !objp ) nprintf(("Network", "Couldn't find object for ai update\n")); switch( code ) { case AI_UPDATE_DOCK: GET_USHORT( other_net_signature ); GET_DATA( docker_index ); GET_DATA( dockee_index ); other_objp = multi_get_network_object( other_net_signature ); if ( !other_objp ) nprintf(("Network", "Couldn't find other object for ai update on dock\n")); // if we don't have an object to work with, break out of loop if ( !objp || !other_objp || (objp->type != OBJ_SHIP) || (other_objp->type != OBJ_SHIP)){ break; } // don't assign the dock indexes, because they're part of the docking ship's goal code... just dock them // (and besides, we might be docking initially docked ships and we wouldn't have an active goal) ai_do_objects_docked_stuff( objp, docker_index, other_objp, dockee_index ); break; case AI_UPDATE_UNDOCK: GET_USHORT( other_net_signature ); other_objp = multi_get_network_object( other_net_signature ); // if we don't have an object to work with, break out of loop if ( !objp ) break; ai_do_objects_undocked_stuff( objp, other_objp ); break; case AI_UPDATE_ORDERS: GET_INT( int_mode ); mode = int_to_ai_goal_mode(int_mode); GET_INT( submode ); GET_USHORT( other_net_signature ); if ( mode == AI_GOAL_DOCK || mode == AI_GOAL_REARM_REPAIR ) { GET_DATA(docker_index); GET_DATA(dockee_index); } // be sure that we have a ship object!!! if ( !objp || (objp->type != OBJ_SHIP) ) break; // set up the information in the first goal element of the object in question aip = &Ai_info[Ships[objp->instance].ai_index]; aip->active_goal = 0; aigp = &aip->goals[aip->active_goal]; aigp->ai_mode = mode; aigp->ai_submode = submode; // for docking, add the docker and dockee index to the active goal if ( mode == AI_GOAL_DOCK || mode == AI_GOAL_REARM_REPAIR ) { aigp->docker.index = docker_index; aigp->dockee.index = dockee_index; aigp->flags.set(AI::Goal_Flags::Dockee_index_valid); aigp->flags.set(AI::Goal_Flags::Docker_index_valid); } // get a shipname if we can. other_objp = multi_get_network_object( other_net_signature ); if ( other_objp && (other_objp->type == OBJ_SHIP) ) { // get a pointer to the shipname in question. Use the ship_name value in the // ship. We are only using this for HUD display, so I think that using this // method will be fine. aigp->target_name = ai_get_goal_target_name(Ships[other_objp->instance].ship_name, &aigp->target_name_index); // special case for destroy subsystem -- get the ai_info pointer to our target ship // so that we can properly set up what subsystem this ship is attacking. if ( (mode == AI_GOAL_DESTROY_SUBSYSTEM ) && (submode >= 0) ) aip->targeted_subsys = ship_get_indexed_subsys( &Ships[other_objp->instance], submode); } break; default: Int3(); // this Int3() should be temporary nprintf(("Network", "Invalid code for ai update: %d\n", code)); break; } PACKET_SET_SIZE(); } // tell the standalone to move into the MISSION_SYNC_STATE void send_mission_sync_packet(int mode,int start_campaign) { ubyte data[MAX_PACKET_SIZE],is_campaign; int packet_size = 0; Assert((Net_player->flags & NETINFO_FLAG_GAME_HOST) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER)); // build the header and add the sync mode (pre or post briefing) BUILD_HEADER(MISSION_SYNC_DATA); ADD_INT(mode); // if this is a campaign game if(mode == MULTI_SYNC_PRE_BRIEFING){ if(Game_mode & GM_CAMPAIGN_MODE){ // add a byte indicating campaign mode is_campaign = 1; ADD_DATA(is_campaign); // add a byte indicating if we should be starting a campaign or continuing it is_campaign = (ubyte)start_campaign; ADD_DATA(is_campaign); // add the campaign filename ADD_STRING(Netgame.campaign_name); } // otherwise if this is a single mission else { // add a byte indicating single mission mode is_campaign = 0; ADD_DATA(is_campaign); // add the mission filename ADD_STRING(Game_current_mission_filename); } } multi_io_send_reliable(Net_player, data, packet_size); } // move into the MISSION_SYNC state when this is received // this packet is sent only from a game host to a standalone void process_mission_sync_packet(ubyte *data, header *hinfo) { int mode; ubyte campaign_flag; int offset = HEADER_LENGTH; Assert(Game_mode & GM_STANDALONE_SERVER); // if this is a team vs team situation, lock the players send a final team update if(Netgame.type_flags & NG_TYPE_TEAM){ multi_team_host_lock_all(); multi_team_send_update(); } // get the sync mode (pre or post briefing) GET_INT(mode); if(mode == MULTI_SYNC_PRE_BRIEFING){ // get the flag indicating if this is a single mission or a campaign mode GET_DATA(campaign_flag); if(campaign_flag){ // get the flag indicating whether we should be starting a new campaign GET_DATA(campaign_flag); // get the campaign filename GET_STRING(Netgame.campaign_name); // either start a new campaign or continue on to the next mission in the current campaign if(campaign_flag){ multi_campaign_start(Netgame.campaign_name); } else { multi_campaign_next_mission(); } } else { // make sure we remove the campaign mode flag Game_mode &= ~(GM_CAMPAIGN_MODE); // get the single mission filename GET_STRING(Game_current_mission_filename); strcpy_s(Netgame.mission_name,Game_current_mission_filename); } } else if (mode == MULTI_SYNC_POST_BRIEFING) { // process the initial orders now (moved from post_process_mission()in missionparse) mission_parse_fixup_players(); ai_post_process_mission(); } PACKET_SET_SIZE(); // set the correct mode and m ove into the state Multi_sync_mode = mode; gameseq_post_event(GS_EVENT_MULTI_MISSION_SYNC); } // tell a player to merge his mission stats into his alltime stats void send_store_stats_packet(int accept) { ubyte data[10],val; int packet_size = 0; BUILD_HEADER(STORE_MISSION_STATS); // add whether we're accepting or tossing val = (ubyte)accept; ADD_DATA(val); // if I'm the server, send to everyone, else send to the standalone to be rebroadcasted if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ multi_io_send_to_all_reliable(data, packet_size); } else { multi_io_send_reliable(Net_player, data, packet_size); } } void process_store_stats_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; ubyte accept; GET_DATA(accept); PACKET_SET_SIZE(); // if I'm the standalone, rebroadcast. Otherwise, if I'm a client, merge my mission stats with my alltime stats if(Game_mode & GM_STANDALONE_SERVER){ // rebroadcast the packet to all others in the game nprintf(("Network","Standalone received store stats packet - rebroadcasting..\n")); multi_io_send_to_all_reliable(data, offset); } else { if(accept){ // all players should mark the stats as being accepted in the debriefing multi_debrief_stats_accept(); } else { // all players should mark the stats as being "tossed" in the debriefing multi_debrief_stats_toss(); } } } void send_debris_update_packet(object *objp,int code) { ubyte data[MAX_PACKET_SIZE]; ubyte val; int packet_size = 0; BUILD_HEADER(DEBRIS_UPDATE); ADD_USHORT(objp->net_signature); val = (ubyte) code; ADD_DATA(val); // add any extra relevant data switch(code){ case DEBRIS_UPDATE_UPDATE: ADD_VECTOR(objp->pos); // add position ADD_ORIENT(objp->orient); // add orientation ADD_VECTOR(objp->phys_info.vel); // add velocity ADD_VECTOR(objp->phys_info.rotvel); // add rotational velocity break; } multi_io_send_to_all(data, packet_size); } void process_debris_update_packet(ubyte *data, header *hinfo) { ushort net_sig; ubyte code; object bogus_object; object *objp; int offset = HEADER_LENGTH; GET_USHORT(net_sig); GET_DATA(code); objp = NULL; objp = multi_get_network_object(net_sig); if(objp == NULL){ objp = &bogus_object; } switch((int)code){ // update the object case DEBRIS_UPDATE_UPDATE: GET_VECTOR(objp->pos); GET_ORIENT(objp->orient); GET_VECTOR(objp->phys_info.vel); GET_VECTOR(objp->phys_info.rotvel); break; // simply remove it (no explosion) case DEBRIS_UPDATE_REMOVE: if(objp != &bogus_object){ Assert(objp->type == OBJ_DEBRIS); obj_delete(OBJ_INDEX(objp)); } break; // blow it up case DEBRIS_UPDATE_NUKE: if(objp != &bogus_object) debris_hit(objp, nullptr,&objp->pos,1000000.0f, nullptr); break; } PACKET_SET_SIZE(); } // ALAN begin void send_wss_request_packet(short player_id, int from_slot, int from_index, int to_slot, int to_index, int wl_ship_slot, int ship_class, int mode, net_player *p) { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; BUILD_HEADER(WSS_REQUEST_PACKET); // add the request information ADD_SHORT(player_id); ADD_INT(from_slot); ADD_INT(from_index); ADD_INT(to_slot); ADD_INT(to_index); ADD_INT(wl_ship_slot); // only used in weapons loadout ADD_INT(ship_class); ADD_INT(mode); // a standard request if(p == NULL){ multi_io_send_reliable(Net_player, data, packet_size); } // being routed through the standalone to the host of the game else { Assert(Game_mode & GM_STANDALONE_SERVER); multi_io_send_reliable(p, data, packet_size); } } void process_wss_request_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; int from_slot,from_index; int to_slot,to_index; int mode; int wl_ship_slot,ship_class; short player_id; int player_num; // determine who this request is from GET_SHORT(player_id); player_num = find_player_index(player_id); // read in the request data GET_INT(from_slot); GET_INT(from_index); GET_INT(to_slot); GET_INT(to_index); GET_INT(wl_ship_slot); // only used in weapons loadout GET_INT(ship_class); // only used in multi team select GET_INT(mode); PACKET_SET_SIZE(); Assert(player_num != -1); if(player_num == -1){ return; } // if we're the standalone, we have to route this packet to the host of the game if(Game_mode & GM_STANDALONE_SERVER){ send_wss_request_packet(player_id, from_slot, from_index, to_slot, to_index, wl_ship_slot, ship_class, mode, Netgame.host); } // otherwise we're the host and should process the request else { switch(mode){ case WSS_WEAPON_SELECT : wl_drop(from_slot,from_index,to_slot,to_index,wl_ship_slot,player_num); break; case WSS_SHIP_SELECT : multi_ts_drop(from_slot,from_index,to_slot,to_index,ship_class,player_num); break; default: Int3(); } } } void send_wss_update_packet(int team_num,ubyte *wss_data,int size) { ubyte data[MAX_PACKET_SIZE],team; int packet_size = 0; Assert(size <= (MAX_PACKET_SIZE - 10)); BUILD_HEADER(WSS_UPDATE_PACKET); // add the team/pool # this is for team = (ubyte)team_num; ADD_DATA(team); // add the data block size ADD_USHORT(static_cast(size)); // add the data itself memcpy(data + packet_size,wss_data,size); packet_size += size; // if we're also the master of the game (not on a standalone) if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ multi_io_send_to_all_reliable(data, packet_size); } // if we're only the host on the standalone, then send the packet to the standalone to be routed else { multi_io_send_reliable(Net_player, data, packet_size); } } void process_wss_update_packet(ubyte *data, header *hinfo) { ubyte team; ushort size; int player_index, idx; int offset = HEADER_LENGTH; // get the team/pool # GET_DATA(team); // get the data size GET_USHORT(size); // if we're the standalone, then we should be routing this data to all the other clients if(Game_mode & GM_STANDALONE_SERVER){ // read in the data offset += size; PACKET_SET_SIZE(); // determine where this came from player_index = find_player_index(hinfo->id); Assert(player_index != -1); if(player_index < 0){ return; } // route the packet (don't resend it to the host) for(idx=0;idxp_info.team); // sync the interface if this was for my own team if((int)team == Net_player->p_info.team){ multi_ts_sync_interface(); } } } // ALAN END // function to send firing information from the client to the server once they reach // the final sync screen. void send_firing_info_packet() { ubyte data[MAX_PACKET_SIZE]; int packet_size; ubyte plinked, sdual; Assert( !(Net_player->flags & NETINFO_FLAG_AM_MASTER) ); BUILD_HEADER(FIRING_INFO); plinked = (ubyte)((Player_ship->flags[Ship::Ship_Flags::Primary_linked])?1:0); sdual = (ubyte)((Player_ship->flags[Ship::Ship_Flags::Secondary_dual_fire])?1:0); ADD_DATA( plinked ); ADD_DATA( sdual ); multi_io_send_reliable(Net_player, data, packet_size); } void process_firing_info_packet( ubyte *data, header *hinfo ) { int offset, player_num; ubyte plinked, sdual; ship *shipp; // only the master of the game should be dealing with these packets Assert( Net_player->flags & NETINFO_FLAG_AM_MASTER ); offset = HEADER_LENGTH; GET_DATA( plinked ); GET_DATA( sdual ); PACKET_SET_SIZE(); player_num = find_player_index(hinfo->id); if(player_num < 0){ nprintf(("Network","Received firing info packet from unknown player, ignoring\n")); return; } // get the ship pointer for this player and set the flags accordingly. shipp = &(Ships[Objects[Net_players[player_num].m_player->objnum].instance]); shipp->flags.set(Ship::Ship_Flags::Primary_linked, plinked != 0); shipp->flags.set(Ship::Ship_Flags::Secondary_dual_fire, sdual != 0); } // packet to deal with changing status of mission goals. used to be sent every so often from server // to clients, but with addition of reliable sockets, send when complete, invalid, etc. // goal_num is the index into mission_goals. new_status means failed, success, etc. -1 if not used. // valid means goal is changing to invalid(0) or valid(1). only applies if new_status == -1 void send_mission_goal_info_packet( int goal_num, int new_status, int valid ) { ubyte data[MAX_PACKET_SIZE]; int packet_size; BUILD_HEADER(MISSION_GOAL_INFO); ADD_INT(goal_num); ADD_INT(new_status); ADD_INT(valid); multi_io_send_to_all_reliable(data, packet_size); } void process_mission_goal_info_packet( ubyte *data, header *hinfo ) { int offset, goal_num, new_status, valid; offset = HEADER_LENGTH; GET_INT(goal_num); GET_INT(new_status); GET_INT(valid); PACKET_SET_SIZE(); // if new_status != -1, then this is a change in goal status (i.e. goal failed, or is successful) if ( new_status != -1 ){ mission_goal_status_change( goal_num, new_status ); } else { mission_goal_validation_change( goal_num, valid != 0 ); } } void send_player_settings_packet(net_player *p) { ubyte data[MAX_PACKET_SIZE]; ubyte stop; int idx; int packet_size = 0; char val; // build the header BUILD_HEADER(PLAYER_SETTINGS); // add all the data for all the players stop = 0x0; for(idx=0;idx(Net_players[idx].p_info.ship_index); ADD_DATA(val); ADD_SHORT(static_cast(Net_players[idx].p_info.ship_class)); } } // add the stop byte stop = 0xff; ADD_DATA(stop); // either broadcast the data or send to a specific player if(p == NULL){ multi_io_send_to_all_reliable(data, packet_size); } else { multi_io_send_reliable(p, data, packet_size); } } void process_player_settings_packet(ubyte *data, header *hinfo) { int offset,player_num; net_player_info bogus,*ptr; short player_id; ubyte stop; short ship_class; char ship_index; offset = HEADER_LENGTH; // read in the data for all the players GET_DATA(stop); while(stop != 0xff){ // lookup the player GET_SHORT(player_id); player_num = find_player_index(player_id); // make sure this is a valid player if(player_num == -1){ ptr = &bogus; } else { ptr = &Net_players[player_num].p_info; } GET_INT(ptr->team); GET_DATA(ship_index); ptr->ship_index = ship_index; GET_SHORT(ship_class); ptr->ship_class = ship_class; // next stop byte GET_DATA(stop); } PACKET_SET_SIZE(); // update the server with my new state // MWA -- 3/31/98 -- check for in mission instead of state. //if ( Netgame.game_state == NETGAME_STATE_MISSION_SYNC) { if( !(Game_mode & GM_IN_MISSION) ) { Net_player->state = NETPLAYER_STATE_SETTINGS_ACK; send_netplayer_update_packet(); } // display some cool text multi_common_add_text(XSTR("Received player settings packet\n",721),1); } void send_deny_packet(net_addr *addr, int code) { ubyte data[10]; int packet_size = 0; // build the header and add the rejection code BUILD_HEADER(DENY); ADD_INT(code); // send the packet psnet_send(addr, data, packet_size); } void process_deny_packet(ubyte *data, header *hinfo) { int offset,code; // get the denial code offset = HEADER_LENGTH; GET_INT(code); PACKET_SET_SIZE(); // if there is already a dialog active, do nothing - who cares at this point. if(popup_active()){ return; } // display the appropriate dialog switch(code){ case JOIN_DENY_JR_STATE : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because the game is not in an appropriate state to accept",722)); break; case JOIN_DENY_JR_TRACKER_INVAL : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because this is a Parallax Online game and you are not a registered Parallax Online pilot",723)); break; case JOIN_DENY_JR_PASSWD : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because this is a password protected game",724)); break; case JOIN_DENY_JR_CLOSED : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because this is a closed game and the mission is in progress",725)); break; case JOIN_DENY_JR_TEMP_CLOSED : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because the netgame is forming and the host has temporarily closed it",726)); break; case JOIN_DENY_JR_RANK_HIGH : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because this is a rank limited game and your rank is too high",727)); break; case JOIN_DENY_JR_RANK_LOW : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because this is a rank limited game and your rank is too low",728)); break; case JOIN_DENY_JR_DUP : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because there is an identical player already in the game",729)); break; case JOIN_DENY_JR_FULL : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because the game is full",730)); break; case JOIN_DENY_JR_BANNED : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because you are banned from this server",731)); break; case JOIN_DENY_JR_NOOBS : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You have been rejected because this game does not allow observers",732)); break; case JOIN_DENY_JR_INGAME_JOIN : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You cannot join at this time since someone else is currently joining. Try again shortly.",733)); break; case JOIN_DENY_JR_BAD_VERSION : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You cannot join this game because you are running an older version of FreeSpace than the server. Exit FreeSpace, and choose the 'Update FreeSpace' button in the FreeSpace launcher to download the latest version of FreeSpace.",734)); break; case JOIN_DENY_JR_TYPE : popup(PF_USE_AFFIRMATIVE_ICON,1,POPUP_OK,XSTR("You cannot join an in progress squadwar mission.",1433)); break; } // call this so that the join request timestamp automatically expires when we hear back from the server multi_join_reset_join_stamp(); } // this packet will consist of // 1.) netplayer ship classes (85 bytes max) // 2.) ship weapon state data (277 bytes max) // 3.) player settings et. al. (133 bytes max) // TOTAL 495 NOTE : keep this in mind when/if adding new data to this packet void send_post_sync_data_packet(net_player *p, int std_request) { ubyte data[MAX_PACKET_SIZE], val; char bval; ship *shipp; net_player *pl; ship_obj *so; ushort sval, ship_ets; int idx, player_index; int packet_size = 0; int ship_count; BUILD_HEADER(POST_SYNC_DATA); // some header information for standalone packet routing purposes val = (ubyte)std_request; ADD_DATA(val); // the standalone has two situations // 1.) sending a request to the host to distribute this block of data // 2.) having recevied this block of data from the host, it redistributes it if((Game_mode & GM_STANDALONE_SERVER) && std_request && (Netgame.host != NULL)){ // case 1, send the request multi_io_send_reliable(Netgame.host, data, packet_size); return; } // case 2 for the standalone is below (as normal) // otherwise build the data now // add all deleted ships val = (ubyte)Multi_ts_num_deleted; ADD_DATA(val); for(idx=0;idxobjnum].flags[Object::Object_Flags::Should_be_dead]) continue; shipp = &Ships[Objects[so->objnum].instance]; // don't process non player wing ships if ( !(shipp->flags[Ship::Ship_Flags::From_player_wing] ) ) continue; ship_count++; } // # of ships - used multiple times in the packet val = (ubyte)ship_count; ADD_DATA(val); // add ship class information (85 bytes max) for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; shipp = &Ships[Objects[so->objnum].instance]; // don't process non player wing ships if ( !(shipp->flags[Ship::Ship_Flags::From_player_wing] ) ) continue; // add the net signature of the object for look up ADD_USHORT( Objects[so->objnum].net_signature ); // add the ship info index ADD_SHORT(static_cast(shipp->ship_info_index)); // add the ships's team select index val = (ubyte)shipp->ts_index; ADD_DATA(val); } // add weapon state information for all starting ships (277 bytes max) for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) { if (Objects[so->objnum].flags[Object::Object_Flags::Should_be_dead]) continue; shipp = &Ships[Objects[so->objnum].instance]; // don't process non player wing ships if ( !(shipp->flags[Ship::Ship_Flags::From_player_wing] ) ) continue; // if this is a ship owned by a player, we should mark down his weapons bank/link settings now if we're the server pl = NULL; if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ player_index = multi_find_player_by_net_signature(Objects[so->objnum].net_signature); if(player_index == -1){ pl = NULL; } else { pl = &Net_players[player_index]; } } // add the net signature and other weapon information ADD_USHORT( Objects[so->objnum].net_signature ); // add number of primary and secondary banks bval = static_cast(shipp->weapons.num_primary_banks); ADD_DATA(bval); bval = static_cast(shipp->weapons.num_secondary_banks); ADD_DATA(bval); // add weapon bank status bval = static_cast(shipp->weapons.current_primary_bank); if (pl != nullptr) { pl->s_info.cur_primary_bank = bval; } // Assert(bval != -1); ADD_DATA(bval); bval = static_cast(shipp->weapons.current_secondary_bank); if (pl != nullptr) { pl->s_info.cur_secondary_bank = bval; } // Assert(bval != -1); ADD_DATA(bval); // primary weapon info for (idx = 0; idx < shipp->weapons.num_primary_banks; idx++) { ADD_SHORT(static_cast(shipp->weapons.primary_bank_weapons[idx])); } // secondary weapon info for (idx = 0; idx < shipp->weapons.num_secondary_banks; idx++) { ADD_SHORT(static_cast(shipp->weapons.secondary_bank_weapons[idx])); ADD_SHORT(static_cast(shipp->weapons.secondary_bank_ammo[idx])); } // send primary and secondary weapon link status val = 0x0; if(shipp->flags[Ship::Ship_Flags::Primary_linked]){ if(pl != NULL){ pl->s_info.cur_link_status |= (1<<0); } val |= (1<<0); } if(shipp->flags[Ship::Ship_Flags::Secondary_dual_fire]){ if(pl != NULL){ pl->s_info.cur_link_status |= (1<<1); } val |= (1<<1); } // if this is a player ship add (1<<2) if(Objects[shipp->objnum].flags[Object::Object_Flags::Player_ship]){ val |= (1<<2); } ADD_DATA(val); // add a ship ets value ship_ets = 0x0000; // shield ets ship_ets |= ((ushort)shipp->shield_recharge_index << 8); // weapon ets ship_ets |= ((ushort)shipp->weapon_recharge_index << 4); // engine ets ship_ets |= ((ushort)shipp->engine_recharge_index); ADD_USHORT(ship_ets); } // 2 cases, if I'm the host on a standalone, I should be sending this to the standalone only // or if I'm the server as well as the host, I should be sending this to all players if(Net_player->flags & NETINFO_FLAG_GAME_HOST){ if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ // broadcast if(p == NULL){ multi_io_send_to_all_reliable(data, packet_size); } // send to a specific player else { multi_io_send_reliable(p, data, packet_size); } } else { multi_io_send_reliable(Net_player, data, packet_size); } } // standalone mode else { // broadcast if(p == NULL){ multi_io_send_to_all_reliable(data, packet_size); } // send to a specific player else { multi_io_send_reliable(p, data, packet_size); } } } void process_post_sync_data_packet(ubyte *data, header *hinfo) { ubyte val, ts_index; char b; ushort net_sig, ship_ets, sval; ship *shipp; object *objp; int idx, j; int offset = HEADER_LENGTH; int ship_count; short val_short, sinfo_index; // packet routing information GET_DATA(val); // if I'm the host on a standalone, this is going to be a request to send the data to the standalone to be routed if((Net_player->flags & NETINFO_FLAG_GAME_HOST) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER) && val){ PACKET_SET_SIZE(); // at this point we want to delete all necessary ships, change all necessary ship classes, and set all weapons up multi_ts_create_wings(); // send to the standalone through my socket send_post_sync_data_packet(Net_player); return; } // process player // add all deleted ships GET_DATA(val); Multi_ts_num_deleted = (int)val; for(idx=0;idxinstance], Ship::Exit_Flags::Player_deleted); obj_delete(Multi_ts_deleted_objnums[idx]); ship_wing_cleanup(objp->instance,&Wings[Ships[objp->instance].wingnum]); } else { Multi_ts_num_deleted--; nprintf(("Network","Couldn't find object by net signature for ship delete in post sync data packet\n")); } } // ship count GET_DATA(val); ship_count = val; // process ship class information for(idx=0; idxtype == OBJ_SHIP, "type: %d, idx: %d, ship_count: %d, sinfo_index: %u, ts_index: %u, net_sig: %u", objp->type, idx, ship_count, sinfo_index, ts_index, net_sig); // set the ship to be the right class change_ship_type(objp->instance,(int)sinfo_index); // set the ship's team select index Ships[objp->instance].ts_index = (int)ts_index; } // process ship weapon state info for(idx=0; idxtype == OBJ_SHIP)); // get a pointer to the ship shipp = &Ships[objp->instance]; // get number of primary and secondary banks; GET_DATA(b); Assert( b != -1 ); shipp->weapons.num_primary_banks = static_cast(b); GET_DATA(b); Assert( b != -1 ); shipp->weapons.num_secondary_banks = static_cast(b); // get bank selection info GET_DATA(b); if ( b == -1 ){ b = 0; } shipp->weapons.current_primary_bank = (int)b; GET_DATA(b); if ( b == -1 ){ b = 0; } shipp->weapons.current_secondary_bank = (int)b; // primary weapon info for (j = 0; j < shipp->weapons.num_primary_banks; j++) { GET_SHORT(val_short); shipp->weapons.primary_bank_weapons[j] = static_cast(val_short); shipp->weapons.primary_bank_start_ammo[j] = (int)std::lround(Ship_info[shipp->ship_info_index].primary_bank_ammo_capacity[j] / Weapon_info[shipp->weapons.primary_bank_weapons[j]].cargo_size); } // secondary weapon info for (j = 0; j < shipp->weapons.num_secondary_banks; j++) { GET_SHORT(val_short); shipp->weapons.secondary_bank_weapons[j] = static_cast(val_short); GET_SHORT(val_short); shipp->weapons.secondary_bank_ammo[j] = static_cast(val_short); shipp->weapons.secondary_bank_start_ammo[j] = (int)std::lround(Ship_info[shipp->ship_info_index].secondary_bank_ammo_capacity[j] / Weapon_info[shipp->weapons.secondary_bank_weapons[j]].cargo_size); } // other flags val = 0x0; GET_DATA(val); if(val & (1<<0)){ shipp->flags.set(Ship::Ship_Flags::Primary_linked); } if(val & (1<<1)){ shipp->flags.set(Ship::Ship_Flags::Secondary_dual_fire); } Objects[shipp->objnum].flags.remove(Object::Object_Flags::Player_ship); Objects[shipp->objnum].flags.remove(Object::Object_Flags::Could_be_player); if(val & (1<<2)){ Objects[shipp->objnum].flags.set(Object::Object_Flags::Player_ship); } else { obj_set_flags( &Objects[shipp->objnum], Objects[shipp->objnum].flags + Object::Object_Flags::Could_be_player); } // get ship ets GET_USHORT(ship_ets); // shield, weapons, and engines ets in that order set_recharge_rates(&Objects[shipp->objnum], ((ship_ets & 0x0f00) >> 8), ((ship_ets & 0x00f0) >> 4), (ship_ets & 0x000f)); } PACKET_SET_SIZE(); // ack the server Net_player->state = NETPLAYER_STATE_POST_DATA_ACK; send_netplayer_update_packet(); // the standalone server will receive this packet from the host of the game, to be applied locally and // also to be rebroadcast. if(Game_mode & GM_STANDALONE_SERVER){ // update player ets settings for(idx=0;idxobjnum != -1)){ multi_server_update_player_weapons(&Net_players[idx],&Ships[Objects[Net_players[idx].m_player->objnum].instance]); } } send_post_sync_data_packet(NULL,0); } } void send_wss_slots_data_packet(int team_num,int final,net_player *p,int std_request) { ubyte data[MAX_PACKET_SIZE],val; short val_short; int idx,i; int packet_size = 0; // build the header BUILD_HEADER(WSS_SLOTS_DATA); // some header information for standalone packet routing purposes val = (ubyte)std_request; ADD_DATA(val); // add the team # val = (ubyte)team_num; ADD_DATA(val); // add whether this is the final packet or not val = (ubyte)final; ADD_DATA(val); // the standalone has two situations // 1.) sending a request to the host to distribute this block of data // 2.) having recevied this block of data from the host, it redistributes it if((Game_mode & GM_STANDALONE_SERVER) && std_request){ // case 1, send the request multi_io_send_reliable(Netgame.host, data, packet_size); return; } // case 2 for the standalone is below (as normal) // add all the slots val = MULTI_TS_NUM_SHIP_SLOTS; ADD_DATA(val); val = MAX_SHIP_WEAPONS; ADD_DATA(val); for(idx=0;idx(Wss_slots_teams[team_num][idx].ship_class); ADD_SHORT(val_short); // add the weapons and counts for(i = 0;i(Wss_slots_teams[team_num][idx].wep[i]); ADD_SHORT(val_short); val_short = static_cast(Wss_slots_teams[team_num][idx].wep_count[i]); ADD_SHORT(val_short); } } // 2 cases, if I'm the host on a standalone, I should be sending this to the standalone only // or if I'm the server as well as the host, I should be sending this to all players if(Net_player->flags & NETINFO_FLAG_GAME_HOST){ if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ // broadcast if(p == NULL){ multi_io_send_to_all_reliable(data, packet_size); } // send to a specific player else { multi_io_send_reliable(p, data, packet_size); } } else { multi_io_send_reliable(Net_player, data, packet_size); } } // standalone mode else { // broadcast if(p == NULL){ multi_io_send_to_all_reliable(data, packet_size); } // send to a specific player else { multi_io_send_reliable(p, data, packet_size); } } } void process_wss_slots_data_packet(ubyte *data, header *hinfo) { ubyte val, team_num, final, num_ship_slots, num_ship_weapons; int idx ,i, j; int offset = HEADER_LENGTH; short val_short, b1, b2; // packet routing information GET_DATA(val); // get team data GET_DATA(team_num); // get whether this is the final packet or not GET_DATA(final); // if I'm the host on a standalone, this is going to be a request to send the data to the standalone to be routed if((Net_player->flags & NETINFO_FLAG_GAME_HOST) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER) && val){ PACKET_SET_SIZE(); // send to the standalone through my socket send_wss_slots_data_packet((int)team_num,(int)final,Net_player); return; } // read in all the slot data GET_DATA(num_ship_slots); GET_DATA(num_ship_weapons); // reset unit data for (i = 0; i < MAX_WSS_SLOTS; i++) { Wss_slots_teams[team_num][i].ship_class = -1; for (j = 0; j < MAX_SHIP_WEAPONS; j++) { Wss_slots_teams[team_num][i].wep[j] = -1; Wss_slots_teams[team_num][i].wep_count[j] = 0; } } for (idx = 0; idx < num_ship_slots; idx++) { // get the ship class GET_SHORT(val_short); if (idx < MAX_WSS_SLOTS) { Wss_slots_teams[team_num][idx].ship_class = val_short; } // get the weapons and counts for (i = 0; i < num_ship_weapons; i++) { GET_SHORT(b1); GET_SHORT(b2); if ( (idx < MAX_WSS_SLOTS) && (i < MAX_SHIP_WEAPONS) ) { Wss_slots_teams[team_num][idx].wep[i] = b1; Wss_slots_teams[team_num][idx].wep_count[i] = b2; } } } PACKET_SET_SIZE(); // update my netplayer state if this is the final packet if(final){ Net_player->state = NETPLAYER_STATE_WSS_ACK; send_netplayer_update_packet(); } // the standalone server will receive this packet from the host of the game, to be applied locally and // also to be rebroadcast. if(Game_mode & GM_STANDALONE_SERVER){ send_wss_slots_data_packet((int)team_num,(int)final,NULL,0); } else { // add some mission sync text multi_common_add_text(XSTR("Weapon slots packet\n",735),1); } } #define OBJ_VISIBILITY_DOT 0.6f // send and receive packets for shield explosion information void send_shield_explosion_packet( int objnum, int tri_num, vec3d /*hit_pos*/ ) { int packet_size, i; ubyte data[MAX_PACKET_SIZE], utri_num; Int3(); // Assert(!(Netgame.debug_flags & NETD_FLAG_CLIENT_NODAMAGE)); Assert( tri_num < UCHAR_MAX ); utri_num = (ubyte)tri_num; // for each player, determine if this object is behind the player -- if so, don't // send the packet. for ( i = 0; i < MAX_PLAYERS; i++ ) { if ( MULTI_CONNECTED(Net_players[i]) && (&Net_players[i] != Net_player) ) { float dot; vec3d eye_to_obj_vec, diff, eye_pos; matrix eye_orient; eye_pos = Net_players[i].s_info.eye_pos; eye_orient = Net_players[i].s_info.eye_orient; // check for same vectors vm_vec_sub(&diff, &Objects[objnum].pos, &eye_pos); if ( vm_vec_mag_quick(&diff) < 0.01 ){ continue; } vm_vec_normalized_dir(&eye_to_obj_vec, &Objects[objnum].pos, &eye_pos); dot = vm_vec_dot(&eye_orient.vec.fvec, &eye_to_obj_vec); if ( dot < OBJ_VISIBILITY_DOT ){ continue; } BUILD_HEADER(SHIELD_EXPLOSION); ADD_USHORT( Objects[objnum].net_signature ); ADD_DATA(utri_num); multi_io_send(&Net_players[i], data, packet_size); } } } void add_shield_point_multi(int objnum, int tri_num, vec3d *hit_pos); void process_shield_explosion_packet( ubyte *data, header *hinfo) { int offset; ushort signature; ubyte utri_num; object *objp; // get the shield hit data offset = HEADER_LENGTH; GET_USHORT(signature); GET_DATA(utri_num); //GET_DATA(hit_pos); PACKET_SET_SIZE(); // find the object with this signature. If found, then do a shield explosion objp = multi_get_network_object( signature ); if ( objp ) { polymodel *pm; shield_info *shieldp; shield_tri stri; vec3d hit_pos; int i; // given the tri num, find the local position which is the average of the // three vertices of the triangle affected. Use this average point as the hit // point // Assert( objp->type == OBJ_SHIP ); if(objp->type != OBJ_SHIP){ return; } pm = model_get(Ship_info[Ships[objp->instance].ship_info_index].model_num); shieldp = &pm->shield; Assert( utri_num < shieldp->ntris ); stri = shieldp->tris[utri_num]; vm_vec_zero(&hit_pos); for ( i = 0; i < 3; i++ ) { vm_vec_add2( &hit_pos, &(shieldp->verts[stri.verts[i]].pos) ); } vm_vec_scale( &hit_pos, 1.0f/3.0f ); add_shield_point_multi( OBJ_INDEX(objp), utri_num, &hit_pos ); } } #define MAX_SHIPS_PER_PACKET ((MAX_PACKET_SIZE-21)/static_cast(sizeof(std::int32_t))) void send_player_stats_block_packet(net_player *pl, int stats_code, net_player *target, const int offset, const int count) { scoring_struct *sc; ubyte data[MAX_PACKET_SIZE], val; int idx, len; int packet_size = 0; ushort u_tmp; int i_tmp; sc = &pl->m_player->stats; // build the header BUILD_HEADER(PLAYER_STATS); // add the player id ADD_SHORT(pl->player_id); // add the byte indicating whether these stats are all-time or not val = (ubyte)stats_code; ADD_DATA(val); // kill information - alltime switch(stats_code){ case STATS_ALLTIME: // alltime kills // Wire protocol packs offset/count as USHORT; bump to wider field if ship_info_size() ever exceeds USHRT_MAX. Assertion(sc->kills.size() <= USHRT_MAX, "ship_info_size() exceeds STATS_ALLTIME_KILLS packet's USHORT field width"); idx = 0; while (idx < sz2i(sc->kills.size())) { send_player_stats_block_packet(pl, STATS_ALLTIME_KILLS, target, idx, sz2i(sc->kills.size())-idx); idx += MAX_SHIPS_PER_PACKET; } Assert( ((int)Medals.size() >= 0) && ((int)Medals.size() < USHRT_MAX) ); ADD_USHORT( (ushort)Medals.size() ); // medal information for(idx=0;idx<(int)Medals.size();idx++){ i_tmp = sc->medal_counts[idx]; ADD_INT(i_tmp); } ADD_INT(sc->score); ADD_INT(sc->rank); ADD_INT(sc->assists); ADD_INT(sc->kill_count); ADD_INT(sc->kill_count_ok); ADD_UINT(sc->p_shots_fired); ADD_UINT(sc->s_shots_fired); ADD_UINT(sc->p_shots_hit); ADD_UINT(sc->s_shots_hit); ADD_UINT(sc->p_bonehead_hits); ADD_UINT(sc->s_bonehead_hits); ADD_INT(sc->bonehead_kills); ADD_UINT(sc->missions_flown); ADD_UINT(sc->flight_time); ADD_INT(sc->last_flown); // should be 32-bit value - taylor ADD_INT(sc->last_backup); // should be 32-bit value - taylor break; case STATS_MISSION: // mission OKkills Assertion(sc->m_okKills.size() <= USHRT_MAX, "ship_info_size() exceeds STATS_MISSION_CLASS_KILLS packet's USHORT field width"); idx = 0; while (idx < sz2i(sc->m_okKills.size())) { send_player_stats_block_packet(pl, STATS_MISSION_CLASS_KILLS, target, idx, sz2i(sc->m_okKills.size())-idx); idx += MAX_SHIPS_PER_PACKET; } ADD_INT(sc->m_score); ADD_INT(sc->m_assists); ADD_INT(sc->m_kill_count); ADD_INT(sc->m_kill_count_ok); ADD_UINT(sc->mp_shots_fired); ADD_UINT(sc->ms_shots_fired); ADD_UINT(sc->mp_shots_hit); ADD_UINT(sc->ms_shots_hit); ADD_UINT(sc->mp_bonehead_hits); ADD_UINT(sc->ms_bonehead_hits); ADD_INT(sc->m_bonehead_kills); ADD_INT(sc->m_player_deaths); ADD_INT(sc->m_medal_earned); break; case STATS_MISSION_KILLS: ADD_INT(sc->m_kill_count); ADD_INT(sc->m_kill_count_ok); ADD_INT(sc->m_assists); break; case STATS_DOGFIGHT_KILLS: val = MAX_PLAYERS; ADD_DATA(val); for(idx=0; idxm_dogfight_kills[idx]; ADD_USHORT(u_tmp); } ADD_INT(sc->m_kill_count); ADD_INT(sc->m_kill_count_ok); ADD_INT(sc->m_assists); break; case STATS_MISSION_CLASS_KILLS: len = std::min(count, MAX_SHIPS_PER_PACKET); ADD_USHORT(static_cast(offset)); ADD_USHORT(static_cast(len)); for (idx = offset; idx < offset + len; idx++) { ADD_INT(sc->m_okKills[idx]); } break; case STATS_ALLTIME_KILLS: len = std::min(count, MAX_SHIPS_PER_PACKET); ADD_USHORT(static_cast(offset)); ADD_USHORT(static_cast(len)); for (idx = offset; idx < offset + len; idx++) { ADD_INT(sc->kills[idx]); } break; } Assert(packet_size < MAX_PACKET_SIZE); // if we're a client, always send the data to the server if(!(Net_player->flags & NETINFO_FLAG_AM_MASTER)){ multi_io_send_reliable(Net_player, data, packet_size); } // otherwise do server specific stuff else { // send to a specific target if(target != NULL){ multi_io_send_reliable(target, data, packet_size); } // otherwise, send to everyone else { multi_io_send_to_all_reliable(data, packet_size); } } } void process_player_stats_block_packet(ubyte *data, header *hinfo) { ubyte val, num_players; int player_num,idx; scoring_struct *sc,bogus; short player_id; int offset = HEADER_LENGTH; ushort u_tmp, num_medals; int i_tmp; ushort si_offset, si_count; // nprintf(("Network","----------++++++++++********RECEIVED STATS***********+++++++++----------\n")); // get the player who these stats are for GET_SHORT(player_id); player_num = find_player_index(player_id); if (player_num == -1) { nprintf(("Network", "Couldn't find player for stats update!\n")); ml_string("Couldn't find player for stats update!\n"); sc = &bogus; Int3(); } else { sc = &Net_players[player_num].m_player->stats; } // get the stats code GET_DATA(val); switch(val){ case STATS_ALLTIME_KILLS: GET_USHORT(si_offset); GET_USHORT(si_count); for (idx = si_offset; idx < si_offset+si_count; idx++) { GET_INT(i_tmp); if (sc->kills.in_bounds(idx)) { sc->kills[idx] = i_tmp; } } break; case STATS_MISSION_CLASS_KILLS: GET_USHORT(si_offset); GET_USHORT(si_count); for (idx = si_offset; idx < si_offset+si_count; idx++) { GET_INT(i_tmp); if (sc->m_okKills.in_bounds(idx)) { sc->m_okKills[idx] = i_tmp; } } break; case STATS_ALLTIME: ml_string("Received STATS_ALLTIME\n"); // read in the stats GET_USHORT( num_medals ); for (idx = 0; idx < num_medals; idx++) { GET_INT(i_tmp); if (idx < (int)Medals.size()) { sc->medal_counts[idx] = i_tmp; } } GET_INT(sc->score); GET_INT(sc->rank); GET_INT(sc->assists); GET_INT(sc->kill_count); GET_INT(sc->kill_count_ok); GET_UINT(sc->p_shots_fired); GET_UINT(sc->s_shots_fired); GET_UINT(sc->p_shots_hit); GET_UINT(sc->s_shots_hit); GET_UINT(sc->p_bonehead_hits); GET_UINT(sc->s_bonehead_hits); GET_INT(sc->bonehead_kills); GET_UINT(sc->missions_flown); GET_UINT(sc->flight_time); GET_INT(sc->last_flown); // should be 32-bit value - taylor GET_INT(sc->last_backup); // should be 32-bit value - taylor break; case STATS_MISSION: ml_string("Received STATS_MISSION\n"); GET_INT(sc->m_score); GET_INT(sc->m_assists); GET_INT(sc->m_kill_count); GET_INT(sc->m_kill_count_ok); GET_UINT(sc->mp_shots_fired); GET_UINT(sc->ms_shots_fired); GET_UINT(sc->mp_shots_hit); GET_UINT(sc->ms_shots_hit); GET_UINT(sc->mp_bonehead_hits); GET_UINT(sc->ms_bonehead_hits); GET_INT(sc->m_bonehead_kills); GET_INT(sc->m_player_deaths); GET_INT(sc->m_medal_earned); break; case STATS_MISSION_KILLS: ml_string("Received STATS_MISSION_KILLS\n"); GET_INT(sc->m_kill_count); GET_INT(sc->m_kill_count_ok); GET_INT(sc->m_assists); break; case STATS_DOGFIGHT_KILLS: ml_string("Received STATS_DOGFIGHT_KILLS\n"); if(player_num >= 0){ ml_printf("Dogfight stats for %s", Net_players[player_num].m_player->callsign); } GET_DATA(num_players); for (idx = 0; idx < num_players; idx++) { GET_USHORT(u_tmp); if (idx < MAX_PLAYERS) { sc->m_dogfight_kills[idx] = u_tmp; if (player_num >= 0) { ml_printf("%d", Net_players[player_num].m_player->stats.m_dogfight_kills[idx]); } } } GET_INT(sc->m_kill_count); GET_INT(sc->m_kill_count_ok); GET_INT(sc->m_assists); break; } PACKET_SET_SIZE(); // if I'm the server of the game, I should always rebroadcast these stats if ((Net_player->flags & NETINFO_FLAG_AM_MASTER) && (sc != &bogus)) { // make sure these are alltime stats Assert(val == STATS_ALLTIME || val == STATS_ALLTIME_KILLS); multi_broadcast_stats(val); } } // called to create asteroid stuff void send_asteroid_create( object *new_objp, object *parent_objp, int asteroid_type, vec3d *relvec ) { int packet_size; ubyte data[MAX_PACKET_SIZE]; ubyte packet_type, atype; vec3d vec; vm_vec_zero(&vec); if (relvec != NULL ) vec = *relvec; BUILD_HEADER( ASTEROID_INFO ); packet_type = ASTEROID_CREATE; Assert( asteroid_type < UCHAR_MAX ); atype = (ubyte)asteroid_type; ADD_DATA( packet_type ); ADD_USHORT( parent_objp->net_signature ); ADD_USHORT( new_objp->net_signature ); ADD_DATA( atype ); ADD_VECTOR( vec ); multi_io_send_to_all(data, packet_size); } void send_asteroid_throw( object *objp ) { int packet_size; ubyte data[MAX_PACKET_SIZE], packet_type; BUILD_HEADER( ASTEROID_INFO ); // this packet type is an asteroid throw packet_type = ASTEROID_THROW; ADD_DATA( packet_type ); ADD_USHORT( objp->net_signature ); ADD_VECTOR( objp->pos ); ADD_VECTOR( objp->phys_info.vel ); multi_io_send_to_all(data, packet_size); } void send_asteroid_hit( object *objp, object *other_objp, vec3d *hitpos, float damage, vec3d* force ) { int packet_size; ubyte data[MAX_PACKET_SIZE], packet_type; if ( hitpos == nullptr ) hitpos = &vmd_zero_vector; if ( force == nullptr ) force = &vmd_zero_vector; // build up an asteroid hit packet BUILD_HEADER( ASTEROID_INFO ); packet_type = ASTEROID_HIT; ADD_DATA( packet_type ); ADD_USHORT( objp->net_signature ); if(other_objp == NULL){ ushort invalid_sig = 0xffff; ADD_USHORT(invalid_sig); } else { ADD_USHORT( other_objp->net_signature ); } ADD_VECTOR( (*hitpos) ); ADD_FLOAT( damage ); ADD_VECTOR( (*force) ); multi_io_send_to_all(data, packet_size); } void process_asteroid_info( ubyte *data, header *hinfo ) { int offset; ubyte packet_type; offset = HEADER_LENGTH; GET_DATA( packet_type ); // based on the packet type, do something interesting with an asteroid! switch( packet_type ) { case ASTEROID_CREATE: { ushort psignature, signature; ubyte atype; vec3d relvec; object *parent_objp; GET_USHORT( psignature ); GET_USHORT( signature ); GET_DATA( atype ); GET_VECTOR( relvec ); // after getting the values, set the next network signature, and call the create sub function multi_set_network_signature( signature, MULTI_SIG_ASTEROID ); parent_objp = multi_get_network_object( psignature ); if ( parent_objp ) { asteroid_sub_create( parent_objp, atype, &relvec ); } else { nprintf(("Network", "Couldn't create asteroid because parent wasn't found!!!\n")); } break; } // asteroid throw packet -- asteroid has wrapped bounds case ASTEROID_THROW: { ushort signature; vec3d pos, vel; object *objp; GET_USHORT( signature ); GET_VECTOR( pos ); GET_VECTOR( vel ); objp = multi_get_network_object( signature ); if ( !objp ) { nprintf(("Network", "Couldn't throw asteroid because couldn't find it\n")); break; } objp->pos = pos; objp->phys_info.vel = vel; objp->phys_info.desired_vel = vel; break; } case ASTEROID_HIT: { ushort signature, osignature; object *objp, *other_objp; vec3d hitpos, force; float damage; GET_USHORT( signature ); GET_USHORT( osignature ); GET_VECTOR( hitpos ); GET_FLOAT( damage ); GET_VECTOR( force ); objp = multi_get_network_object( signature ); if(osignature == 0xffff){ other_objp = NULL; } else { other_objp = multi_get_network_object( osignature ); } if ( !objp ) { nprintf(("Network", "Cannot hit asteroid because signature isn't found\n")); break; } if ( IS_VEC_NULL(&hitpos) ){ asteroid_hit( objp, other_objp, NULL, damage, &force ); } else { asteroid_hit( objp, other_objp, &hitpos, damage, &force); } // if we know the other object is a weapon, then do a weapon hit to kill the weapon if ( other_objp && (other_objp->type == OBJ_WEAPON) ){ bool armed = weapon_hit( other_objp, objp, &hitpos ); maybe_play_conditional_impacts({}, other_objp, objp, armed, -1, &hitpos); } break; } default: Int3(); break; } PACKET_SET_SIZE(); } void send_host_restr_packet(const char *callsign,int code,int mode) { ubyte data[MAX_PACKET_SIZE],val; int packet_size = 0; // build the header and add the opcode BUILD_HEADER(HOST_RESTR_QUERY); val = (ubyte)code; ADD_DATA(val); val = (ubyte)mode; ADD_DATA(val); // add the callsign ADD_STRING(callsign); // if I'm the standalone server, I should be sending this to the game host if((Game_mode & GM_STANDALONE_SERVER) && (Netgame.host != NULL)){ multi_io_send_reliable(Netgame.host, data, packet_size); } // otherwise if I'm the host, I should be sending a reply back to the standalone server else { Assert(Net_player->flags & NETINFO_FLAG_GAME_HOST); multi_io_send_reliable(Net_player, data, packet_size); } } void process_host_restr_packet(ubyte *data, header *hinfo) { char callsign[255]; ubyte code,mode; int offset = HEADER_LENGTH; // get the opcode and the callsign GET_DATA(code); GET_DATA(mode); GET_STRING(callsign); PACKET_SET_SIZE(); // do code specific operations switch(code) { // query to the host from standalone case 0: Assert((Net_player->flags & NETINFO_FLAG_GAME_HOST) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER)); // set the join mode Multi_join_restr_mode = mode; // set the timestamp Multi_restr_query_timestamp = ui_timestamp(MULTI_QUERY_RESTR_STAMP); // notify the host of the event gamesnd_play_iface(InterfaceSounds::BRIEF_STAGE_CHG_FAIL); HUD_printf(XSTR("Player %s has tried to join - allow (y/n) ?",736),callsign); break; // affirmative reply from the host to the standalone case 1: Assert(Game_mode & GM_STANDALONE_SERVER); // let the player join if the timestamp has not already elapsed on the server if(Multi_restr_query_timestamp.isValid()){ multi_process_valid_join_request(&Multi_restr_join_request,&Multi_restr_addr,(int)mode); } break; // negative reply case 2 : Assert(Game_mode & GM_STANDALONE_SERVER); Netgame.flags &= ~(NG_FLAG_INGAME_JOINING); Multi_restr_query_timestamp = UI_TIMESTAMP::invalid(); break; } } void send_netgame_end_error_packet(int notify_code,int err_code) { ubyte data[10]; char code; int packet_size = 0; // only the server should ever be here - although this might change if for some reason the host wants to end the game Assert(Net_player->flags & NETINFO_FLAG_AM_MASTER); // build the header and add the notification and error codes BUILD_HEADER(NETGAME_END_ERROR); code = (char)notify_code; ADD_DATA(code); code = (char)err_code; ADD_DATA(code); // send the packet multi_io_send_to_all_reliable(data, packet_size); } void process_netgame_end_error_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; char notify_code,error_code; // get the error and notification codes GET_DATA(notify_code); GET_DATA(error_code); PACKET_SET_SIZE(); // quit the game multi_quit_game(PROMPT_NONE,notify_code,error_code); } // sends info that a countermeasure succeeded. void send_countermeasure_success_packet( int objnum ) { int pnum, packet_size; ubyte data[MAX_PACKET_SIZE]; pnum = multi_find_player_by_object( &Objects[objnum] ); if ( pnum == -1 ) { nprintf(("Network", "Coulnd't find player for countermeasure success packet\n")); return; } BUILD_HEADER(COUNTERMEASURE_SUCCESS); multi_io_send(&Net_players[pnum], data, packet_size); } // start the flashing of my hud gauge void process_countermeasure_success_packet( ubyte * /*data*/, header *hinfo ) { int offset; offset = HEADER_LENGTH; PACKET_SET_SIZE(); //Do this instead so there's less repeat code //Player_obj is necessary...infinitely recursive function calls != FTW cmeasure_maybe_alert_success(Player_obj); /*hud_start_text_flash(XSTR("Evaded", 1430), 800); snd_play(gamesnd_get_game_sound(SND_MISSILE_EVADED_POPUP));*/ } #define UPDATE_IS_PAUSED (1<<0) #define UPDATE_HULL_INFO (1<<1) void send_client_update_packet(net_player *pl) { ubyte data[MAX_PACKET_SIZE],val; int packet_size = 0; // build the header BUILD_HEADER(CLIENT_UPDATE); val = 0; // add the pause status if ( Multi_pause_status ) { val |= UPDATE_IS_PAUSED; } else if ( (Game_mode & GM_IN_MISSION) && !(pl->flags & NETINFO_FLAG_INGAME_JOIN) && !(NETPLAYER_IS_OBSERVER(pl)) && !(NETPLAYER_IS_DEAD(pl)) && (Objects[pl->m_player->objnum].type == OBJ_SHIP) ) { val |= UPDATE_HULL_INFO; Assert( Player_ship ); // I"d better have one of these!!!! } ADD_DATA(val); // if paused, add the net address of the guy who paused if(val & UPDATE_IS_PAUSED){ Assert(Multi_pause_pauser != NULL); ADD_SHORT(Multi_pause_pauser->player_id); } // when not paused, send hull/shield/subsystem updates to all clients (except for ingame joiners) if ( val & UPDATE_HULL_INFO ) { object *objp; ubyte percent, threats, n_quadrants; ship_info *sip; ship *shipp; ship_subsys *subsysp; int i; // get the object for the player Assert( pl->m_player->objnum != -1 ); objp = &Objects[pl->m_player->objnum]; Assert ( objp->type == OBJ_SHIP ); shipp = &Ships[objp->instance]; sip = &Ship_info[shipp->ship_info_index]; // hull strength and shield mesh information are floats (as a percentage). Pass the integer // percentage value since that should be close enough percent = (ubyte)std::lround (get_hull_pct(objp) * 100.0f); if ( (percent == 0) && (get_hull_pct(objp) > 0.0f) ) { percent = 1; } ADD_DATA( percent ); n_quadrants = static_cast(objp->shield_quadrant.size()); ADD_DATA( n_quadrants ); float max_quad = shield_get_max_quad(objp); for (float quadrant: objp->shield_quadrant) { percent = static_cast(quadrant / max_quad * 100.0f); ADD_DATA( percent ); } // add the data for the subsystem hits. We can assume that the lists will be the same side of // both machines. Added as percent since that number <= 100 // also write out the number of subsystems. We do this because the client might not know // about the object he is getting data for. (i.e. he killed the object already). ADD_USHORT(static_cast(sip->n_subsystems)); // now the subsystems. for ( subsysp = GET_FIRST(&shipp->subsys_list); subsysp != END_OF_LIST(&shipp->subsys_list); subsysp = GET_NEXT(subsysp) ) { percent = (ubyte)(subsysp->current_hits / subsysp->max_hits * 100.0f); ADD_DATA( percent ); } // compute the threats for this player. Only compute the threats if this player is actually // playing (i.e. he has a ship) hud_update_reticle( pl->m_player ); threats = (ubyte)pl->m_player->threat_flags; ADD_DATA( threats ); // add his energy level for guns ADD_FLOAT(shipp->weapon_energy); // add his secondary bank ammo val = static_cast(shipp->weapons.num_secondary_banks); ADD_DATA(val); for(i=0; iweapons.num_secondary_banks; i++){ ADD_SHORT(static_cast(shipp->weapons.secondary_bank_ammo[i])); } } // add pl ADD_INT(pl->sv_last_pl); // send the packet reliably to the player multi_io_send(pl, data, packet_size); } void process_client_update_packet(ubyte *data, header *hinfo) { ubyte val; short pauser; int player_index; int is_paused, have_hull_info; ubyte ammo_count; short ammo[MAX_SHIP_SECONDARY_BANKS]; float weapon_energy; int offset = HEADER_LENGTH; // get the header byte containing useful information GET_DATA(val); is_paused = (val & UPDATE_IS_PAUSED)?1:0; have_hull_info = (val & UPDATE_HULL_INFO)?1:0; // if we are paused, get who paused if(is_paused){ GET_SHORT(pauser); player_index = find_player_index(pauser); if(player_index != -1){ Multi_pause_pauser = &Net_players[player_index]; } else { Multi_pause_pauser = NULL; } } // if we have hull information, then read it in. if ( have_hull_info ) { float fl_val; ship_info *sip; ship *shipp; ubyte hull_percent, n_quadrants, subsystem_percent[MAX_MODEL_SUBSYSTEMS], threats; SCP_vector shield_percent; ubyte ub_tmp; ship_subsys *subsysp; object *objp; int i; ushort n_subsystems; short s_val; // hull strength and shield mesh information are floats (as a percentage). Pass the integer // percentage value since that should be close enough GET_DATA( hull_percent ); GET_DATA( n_quadrants ); shield_percent.resize(n_quadrants); for (i = 0; i < n_quadrants; i++ ){ GET_DATA(ub_tmp); shield_percent[i] = ub_tmp; } // get the data for the subsystems GET_USHORT( n_subsystems ); for ( i = 0; i < n_subsystems; i++ ){ GET_DATA(ub_tmp); subsystem_percent[i] = ub_tmp; } GET_DATA( threats ); // add his energy level for guns GET_FLOAT(weapon_energy); // add his secondary bank ammo GET_DATA(ammo_count); for(i=0; i MAX_SHIP_SECONDARY_BANKS) { ammo_count = MAX_SHIP_SECONDARY_BANKS; } // assign the above information to my ship, assuming that I can find it! Ingame joiners might get this // packet because of delay between reliable packet acknowledging my ingame ship and the start of these // UDP client update packets. Only read this info if I have a ship. if ( !(Net_player->flags & NETINFO_FLAG_INGAME_JOIN) && (Player_ship != NULL) && (Player_obj != NULL) && (Net_player != NULL)) { shipp = Player_ship; objp = Player_obj; sip = &Ship_info[shipp->ship_info_index]; fl_val = hull_percent * shipp->ship_max_hull_strength / 100.0f; objp->hull_strength = fl_val; float max_quad = shield_get_max_quad(objp); for ( i = 0; i < n_quadrants; i++ ) { fl_val = (shield_percent[i] * max_quad / 100.0f); objp->shield_quadrant[i] = fl_val; } // for sanity, be sure that the number of susbystems that I read in matches the player. If not, // then don't read these in. if ( n_subsystems == sip->n_subsystems ) { n_subsystems = 0; // reuse this variable for ( subsysp = GET_FIRST(&shipp->subsys_list); subsysp != END_OF_LIST(&shipp->subsys_list); subsysp = GET_NEXT(subsysp) ) { int subsys_type; fl_val = subsystem_percent[n_subsystems] * subsysp->max_hits / 100.0f; subsysp->current_hits = fl_val; // add the value just generated (it was zero'ed above) into the array of generic system types subsys_type = subsysp->system_info->type; // this is the generic type of subsystem Assert ( subsys_type < SUBSYSTEM_MAX ); if (!(subsysp->flags[Ship::Subsystem_Flags::No_aggregate])) { shipp->subsys_info[subsys_type].aggregate_current_hits += fl_val; } n_subsystems++; } } ship_recalc_subsys_strength( shipp ); shipp->weapon_energy = weapon_energy; for(i=0; iweapons.secondary_bank_ammo[i] = ammo[i]; } // update my threat flags. // temporarily commented out until tested. Net_player->m_player->threat_flags = threats; } } // get pl int pl; GET_INT(pl); if(Net_player != NULL){ Net_player->cl_last_pl = pl; } PACKET_SET_SIZE(); // note, if we're already paused or unpaused, calling these will have no effect, so it is safe to do so if(!popup_active() && !(Net_player->flags & NETINFO_FLAG_RESPAWNING) && !(Net_player->flags & NETINFO_FLAG_LIMBO)){ if( is_paused ) { multi_pause_pause(); } else { multi_pause_unpause(); } } } void send_countdown_packet(int time) { ubyte data[20]; char val; int packet_size = 0; // build the header and add the time BUILD_HEADER(COUNTDOWN); val = (char)time; ADD_DATA(val); // if we're the server, we should broadcast to everyone if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ multi_io_send_to_all_reliable(data, packet_size); } // otherwise we'de better be a host sending to the standalone else { Assert(Net_player->flags & NETINFO_FLAG_GAME_HOST); multi_io_send_reliable(Net_player, data, packet_size); } } void process_countdown_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; char time; // get the time GET_DATA(time); PACKET_SET_SIZE(); // if we're not in the post sync data screen, ignore it if(gameseq_get_state() != GS_STATE_MULTI_MISSION_SYNC){ return; } // if we're the standalone, this should be a -1 telling us to start the countdown if(Game_mode & GM_STANDALONE_SERVER){ Assert((int)time == -1); // start the countdown multi_sync_start_countdown(); } // otherwise if we're clients, just bash the countdown else { Multi_sync_countdown = (int)time; } } // packets for debriefing information void send_debrief_info( int stage_count[], int *stages[] ) { ubyte data[MAX_PACKET_SIZE]; int packet_size, i, j; int i_tmp; BUILD_HEADER(DEBRIEF_INFO); // add the data for the teams for ( i = 0; i < Num_teams; i++ ) { int count; count = stage_count[i]; ADD_INT( count ); for ( j = 0; j < count; j++ ) { i_tmp = stages[i][j]; ADD_INT( i_tmp ); } } multi_io_send_to_all_reliable(data, packet_size); } // process a debrief info packet from the server void process_debrief_info( ubyte *data, header *hinfo ) { int offset, i, j; int stage_counts[MAX_TVT_TEAMS], active_stages[MAX_TVT_TEAMS][MAX_DEBRIEF_STAGES], *stages[MAX_TVT_TEAMS]; int i_tmp; offset = HEADER_LENGTH; for ( i = 0; i < Num_teams; i++ ) { int count; GET_INT( count ); stage_counts[i] = count; stages[i] = active_stages[i]; for ( j = 0; j < count; j++ ) { GET_INT(i_tmp); active_stages[i][j] = i_tmp; } } PACKET_SET_SIZE(); // now that we have the stage data for the debriefing stages, call debrief function with the // data so that clients can now see the debriefing stuff. Do it only for my team. Assert( (Net_player->p_info.team >= 0) && (Net_player->p_info.team < Num_teams) ); debrief_set_multi_clients( stage_counts[Net_player->p_info.team], stages[Net_player->p_info.team] ); } constexpr ubyte HWIF_BIG_UPDATE = (1 << 0); // sends homing information to all clients. We only need signature and num_missiles (because of hornets). // sends homing_object and homing_subsystem to all clients. void send_homing_weapon_info( int weapon_num ) { ubyte data[MAX_PACKET_SIZE], flags = 0; int s_index; int packet_size; object *homing_object; ushort homing_signature; weapon *wp; wp = &Weapons[weapon_num]; // be sure that this weapon object is a homing object. if ( !(Weapon_info[wp->weapon_info_index].is_homing()) ) return; // default the subsystem s_index = -1; // get the homing signature. If this weapon isn't homing on anything, then sent 0 as the // homing signature. homing_signature = 0; homing_object = wp->homing_object; if ( homing_object != &obj_used_list ) { homing_signature = homing_object->net_signature; // get the subsystem index. if ( (homing_object->type == OBJ_SHIP) && (wp->homing_subsys != NULL) ) { s_index = ship_get_subsys_index( wp->homing_subsys ); } } if (!wp->weapon_flags[Weapon::Weapon_Flags::Multi_Update_Sent] || IS_MAT_NULL(&Objects[wp->objnum].phys_info.ai_desired_orient)) { flags |= HWIF_BIG_UPDATE; wp->weapon_flags.set(Weapon::Weapon_Flags::Multi_Update_Sent); } BUILD_HEADER(HOMING_WEAPON_UPDATE); ADD_DATA(flags); ADD_USHORT( Objects[wp->objnum].net_signature ); ADD_USHORT( homing_signature ); ADD_SHORT( static_cast(s_index) ); ADD_VECTOR( wp->homing_pos); if (flags & HWIF_BIG_UPDATE) { fix current_lifetime = Missiontime - wp->creation_time; ADD_INT(current_lifetime); ADD_VECTOR(Objects[wp->objnum].pos); ADD_FLOAT(wp->launch_speed); ADD_ORIENT(Objects[wp->objnum].orient); } multi_io_send_to_all(data, packet_size); } // process a homing weapon info change packet. multiple_missiles parameter specifies is this // packet contains information for multiple weapons (like hornets). void process_homing_weapon_info( ubyte *data, header *hinfo ) { ubyte flags; int offset; fix missile_lifetime = 0; ushort weapon_signature, homing_signature; short h_subsys; float launch_speed = 0.0f; vec3d missile_pos = vmd_zero_vector, homing_goal = vmd_zero_vector; object *homing_object, *weapon_objp; weapon *wp; matrix orient_in = vmd_identity_matrix; offset = HEADER_LENGTH; // get the data for the packet GET_DATA(flags); GET_USHORT( weapon_signature ); GET_USHORT( homing_signature ); GET_SHORT( h_subsys ); GET_VECTOR( homing_goal ); if (flags & HWIF_BIG_UPDATE) { GET_INT(missile_lifetime); GET_VECTOR(missile_pos); GET_FLOAT(launch_speed); GET_ORIENT(orient_in); } PACKET_SET_SIZE(); // deal with changing this weapons homing information weapon_objp = multi_get_network_object( weapon_signature ); if ( weapon_objp == nullptr ) { nprintf(("Network", "Couldn't find weapon object for homing update -- skipping update\n")); return; } Assert( weapon_objp->type == OBJ_WEAPON ); wp = &Weapons[weapon_objp->instance]; // be sure that we can find these weapons and homing_object = multi_get_network_object( homing_signature ); if ( homing_object == nullptr ) { wp->homing_object = &obj_used_list; wp->homing_subsys = nullptr; wp->target_num = -1; wp->target_sig = -1; nprintf(("Network", "Couldn't find homing object for homing update\n")); return; } if ( homing_object->type == OBJ_WEAPON ) { auto flags_check = Weapon_info[Weapons[homing_object->instance].weapon_info_index].wi_flags; if (flags_check.none_of(Weapon::Info_Flags::Bomb, Weapon::Info_Flags::Cmeasure)) { nprintf(("Network", "Homing object is invalid for homing update\n")); return; } } wp->homing_object = homing_object; wp->homing_subsys = nullptr; wp->target_num = OBJ_INDEX(homing_object); wp->target_sig = homing_object->signature; wp->homing_pos = homing_goal; if ( h_subsys != -1 ) { Assert( homing_object->type == OBJ_SHIP ); wp->homing_subsys = ship_get_indexed_subsys( &Ships[homing_object->instance], h_subsys); } if (flags & HWIF_BIG_UPDATE) { // the sender packed the missile's age, so walk creation_time back from now to recover it wp->creation_time = Missiontime - missile_lifetime; weapon_objp->pos = missile_pos; weapon_objp->orient = orient_in; wp->launch_speed = launch_speed; } if ( homing_object->type == OBJ_SHIP ) { nprintf(("Network", "Updating homing information for weapon -- homing on %s\n", Ships[homing_object->instance].ship_name)); } } void send_emp_effect(ushort net_sig, float intensity, float time) { ubyte data[25]; int packet_size; Assert(MULTIPLAYER_MASTER); // build the packet and add the opcode BUILD_HEADER(EMP_EFFECT); ADD_USHORT(net_sig); ADD_FLOAT(intensity); ADD_FLOAT(time); // send it to the player multi_io_send_to_all(data, packet_size); } void process_emp_effect(ubyte *data, header *hinfo) { float intensity, time; ushort net_sig; object *objp; int offset = HEADER_LENGTH; // read in the EMP effect data GET_USHORT(net_sig); GET_FLOAT(intensity); GET_FLOAT(time); PACKET_SET_SIZE(); // try and find the object objp = multi_get_network_object(net_sig); if((objp != NULL) && (objp->type == OBJ_SHIP)){ // if i'm not an observer and I have a valid ship, play the EMP effect if(!(Net_player->flags & NETINFO_FLAG_OBSERVER) && (Player_obj != NULL) && (Player_obj->type == OBJ_SHIP) && (Player_obj == objp)){ emp_start_local(intensity, time); } // start the effect for the ship itself emp_start_ship(objp, intensity, time); } } // tells whether or not reinforcements are available void send_reinforcement_avail( int rnum ) { ubyte data[25]; int packet_size; BUILD_HEADER(REINFORCEMENT_AVAIL); ADD_INT( rnum ); multi_io_send_to_all_reliable(data, packet_size); } void process_reinforcement_avail( ubyte *data, header *hinfo ) { int offset; int rnum; offset = HEADER_LENGTH; GET_INT( rnum ); PACKET_SET_SIZE(); // sanity check for a valid reinforcement number if (Reinforcements.in_bounds(rnum)) { Reinforcements[rnum].flags |= RF_IS_AVAILABLE; } } // Sends a primary fire shot from any ship on the server, or as a backup packet type for the client // if reference ship cannot be found. void send_NEW_primary_fired_packet(ship *shipp, int banks_fired) { // in case nothing got fired, we don't have to do anything. if(banks_fired <= 0){ return; } int packet_size, objnum; ubyte data[MAX_PACKET_SIZE]; object *objp; int np_index; net_player *ignore = nullptr; // get an object pointer for this ship. objnum = shipp->objnum; objp = &Objects[objnum]; // if i'm a multiplayer client, I should never send primary fired packets for anyone except me if (MULTIPLAYER_CLIENT && (Player_obj != objp)){ return; } np_index = multi_find_player_by_net_signature(objp->net_signature); if((np_index >= 0) && (np_index < MAX_PLAYERS)){ ignore = &Net_players[np_index]; } // build up the standard weapon fired packet. This packet will get sent to all players if an AI // ship fired the primary weapons. If a player fired the weapon, then this packet will get sent // to every player but the guy who actullly fired the weapon. This method is used to help keep client // and server in sync w.r.t. weapon energy for player ship BUILD_HEADER( PRIMARY_FIRED_NEW ); ADD_USHORT(objp->net_signature); // if I'm a server, broadcast to all players if(MULTIPLAYER_MASTER){ multi_io_send_to_all(data, packet_size, ignore); // TEST CODE multi_rate_add(1, "wfi", packet_size); } // otherwise just send to the server else { multi_io_send(Net_player, data, packet_size); } } void process_NEW_primary_fired_packet(ubyte *data, header *hinfo) { int offset; object* objp; ushort shooter_sig; // read all packet info offset = HEADER_LENGTH; GET_USHORT(shooter_sig); PACKET_SET_SIZE(); // find the object this fired packet is operating on objp = multi_get_network_object( shooter_sig ); if ( objp == nullptr ) { nprintf(("Network", "Could not find ship for fire primary packet NEW!\n")); return; } // if this object is not actually a valid ship, don't do anything if(objp->type != OBJ_SHIP){ return; } // Juke - also check (objp->instance >= MAX_SHIPS) if(objp->instance < 0 || objp->instance >= MAX_SHIPS){ return; } // if we're in client firing mode, ignore ones for myself if((Player_obj != nullptr) && (Player_obj == objp)){ return; } ship_fire_primary( objp, 1 ); } const int NON_HOMING_PACKET_MISSILE = (1 << 0); // Cyborg17 -- send the relative position of my fire to my current target as well as the frametime. // The whole goal of this is to create the weapon on the server that is heading at your target // in the same exact way that it is on the client. void send_non_homing_fired_packet(ship* shipp, int banks_or_number_of_missiles_fired, bool secondary) { Assertion(MULTIPLAYER_CLIENT, "Server is attempting to send the non_homing_fired_packet, which is client only. \n\nThis is a coder mistake. Please report!"); if (MULTIPLAYER_MASTER) { return; } int packet_size, objnum; ubyte data[MAX_PACKET_SIZE], flags = 0; // ubanks_fired, current_bank; object* objp; // just in case nothing got fired if (banks_or_number_of_missiles_fired <= 0) { return; } // get an object pointer for this ship. objnum = shipp->objnum; objp = &Objects[objnum]; // I should never send non-homing packets for anyone except me if (Player_obj != objp) { return; } object* ref_objp = multi_get_network_object(multi_client_lookup_ref_obj_net_sig()); if (ref_objp == nullptr || ref_objp->type != OBJ_SHIP) { mprintf(("Unable to get accurate reference object for non-homing packet, because %s\n", (ref_objp == nullptr) ? "it was null." : "it was not a ship!")); if (!secondary) { send_NEW_primary_fired_packet(shipp, banks_or_number_of_missiles_fired); } return; } BUILD_HEADER(LINEAR_WEAPON_FIRED); if (secondary) { flags |= NON_HOMING_PACKET_MISSILE; } // Add the shooting object. ADD_USHORT(objp->net_signature); ADD_DATA(flags); ADD_USHORT(ref_objp->net_signature); // We need the time elpased, so send the last frame we got from the server and how much time has happened since then. int last_received_frame = multi_client_lookup_frame_idx(); auto time_elapsed = static_cast(Multi_Timing_Info.get_current_time() - multi_client_lookup_frame_timestamp()); ADD_INT(last_received_frame); ADD_USHORT(time_elapsed); // Save the transposed matrix so that we can optimize and use it twice. matrix ref_ori; vm_copy_transpose(&ref_ori, &ref_objp->orient); vec3d ref_to_ship_vec, temp; // Find the vector between the two objects and normalize. float distance = vm_vec_normalized_dir(&temp, &objp->pos, &ref_objp->pos); ADD_FLOAT(distance); // unrotate via transposed matrix. This is an "unrotate", because the matrix has already been transposed. // This finalized the relative position we will send to the server. vm_vec_rotate(&ref_to_ship_vec, &temp, &ref_ori ); ADD_VECTOR(ref_to_ship_vec); angles adjustment_angles, player_ship_angles; // Save on bandwidth by changing to angles. vm_extract_angles_matrix_alternate(&adjustment_angles, &ref_ori); vm_extract_angles_matrix_alternate(&player_ship_angles, &objp->orient); ADD_FLOAT(adjustment_angles.b); ADD_FLOAT(adjustment_angles.h); ADD_FLOAT(adjustment_angles.p); ADD_FLOAT(player_ship_angles.b); ADD_FLOAT(player_ship_angles.h); ADD_FLOAT(player_ship_angles.p); multi_io_send(Net_player, data, packet_size); } void process_non_homing_fired_packet(ubyte* data, header* hinfo) { int offset; // linked; object* objp; ushort shooter_sig; ubyte flags; bool secondary = false; // read all packet info offset = HEADER_LENGTH; GET_USHORT(shooter_sig); GET_DATA(flags); // find the object this fired packet is operating on objp = multi_get_network_object(shooter_sig); if (flags & NON_HOMING_PACKET_MISSILE) { secondary = true; } ushort time_elapsed; ushort target_ref; int client_frame; angles adjustment_angles, player_ship_angles; float distance; vec3d ref_to_ship_vec; //finish processing the packet GET_USHORT(target_ref); GET_INT(client_frame); GET_USHORT(time_elapsed); GET_FLOAT(distance); GET_VECTOR(ref_to_ship_vec); GET_FLOAT(adjustment_angles.b); GET_FLOAT(adjustment_angles.h); GET_FLOAT(adjustment_angles.p); GET_FLOAT(player_ship_angles.b); GET_FLOAT(player_ship_angles.h); GET_FLOAT(player_ship_angles.p); PACKET_SET_SIZE(); if (objp == nullptr) { nprintf(("Network", "Could not find ship for fire primary packet NEW!\n")); return; } // if this object is not actually a valid ship, don't do anything if (objp->type != OBJ_SHIP) { return; } // Juke - also check (objp->instance >= MAX_SHIPS) if (objp->instance < 0 || objp->instance >= MAX_SHIPS) { return; } object* objp_ref = multi_get_network_object(target_ref); if ((Is_standalone && !Multi_options_g.std_rollback) || !objp_ref || (objp_ref->type != OBJ_SHIP)) { // new way failed, use the old new way. if (objp_ref != nullptr){ mprintf(("Rollback's reference object was not a ship! It was a %d. Get Cyborg!!!\n", objp_ref->type)); } if (secondary) { // if this is a rollback shot from a dumbfire secondary, we have to mark this as a // rollback shot so the client doesn't get an extra shot. ship_fire_secondary(objp, 0, true); } else { ship_fire_primary(objp, 1); } return; } // figure out correct start frame int frame = multi_ship_record_find_frame(client_frame, static_cast(time_elapsed)); if (frame > -1) { // adjust time so that we can interpolate the position and orientation that was seen on the client. int time_after_frame = multi_ship_record_find_time_after_frame(client_frame, frame, static_cast(time_elapsed)); Assertion(time_after_frame >= 0, "Primary fire packet processor found an invalid time_after_frame of %d", time_after_frame); vec3d new_tar_pos = multi_ship_record_lookup_position(objp_ref, frame); matrix new_tar_ori = multi_ship_record_lookup_orientation(objp_ref, frame); // find out where the angle to the new primary fire should be, by // rotating the vector vec3d temp_vec = ref_to_ship_vec; // figure out the new position for the firing ship. vm_vec_rotate(&ref_to_ship_vec, &temp_vec, &new_tar_ori); // multiplay the distance back in vm_vec_scale(&ref_to_ship_vec, distance); // Finish finding the shot's starting position vec3d new_player_pos; vm_vec_add(&new_player_pos, &ref_to_ship_vec, &new_tar_pos); // "decompress" the orientation matrix from the packet's angles. matrix adjust_ship_matrix, old_player_ori; vm_angles_2_matrix(&adjust_ship_matrix, &adjustment_angles); vm_angles_2_matrix(&old_player_ori, &player_ship_angles); // "decompress" in the second way. The firing ship orientation was also stored in the packet because // it was unrotated on the client side using the target's old orientation. matrix temp_ori; vm_matrix_x_matrix(&temp_ori, &new_tar_ori, &adjust_ship_matrix); vm_orthogonalize_matrix(&temp_ori); // Now multiply the two matrices to find the orientation of the firing ship. matrix new_player_ori; vm_matrix_x_matrix(&new_player_ori, &old_player_ori, &temp_ori); multi_ship_record_add_rollback_shot(objp, &new_player_pos, &new_player_ori, frame, secondary); } // if the new way fails for some reason, use the old way. else { nprintf(("Network", "Rollback was not performed because the frame sent by the client is either too old or invalid.. Using the old system.\n")); if (secondary) { // if this is a rollback shot from a dumbfire secondary, we have to mark this as a // rollback shot so the client doesn't get an extra missile for free. ship_fire_secondary(objp, 0, true); } else { ship_fire_primary(objp, 1); } } } static constexpr size_t animation_direction_bit = 1 << 0; static constexpr size_t animation_forced_bit = 1 << 1; static constexpr size_t animation_instant_bit = 1 << 2; static constexpr size_t animation_pause_bit = 1 << 3; void send_animation_triggered_packet(unsigned int animationId, object* parent_object, ushort special_mode, const animation::ModelAnimationDirection& direction, bool force, bool instant, bool pause, const int* /*time*/) { int packet_size; ushort netsig_to_send = 0; ubyte data[MAX_PACKET_SIZE]; Assertion(special_mode != 0 || parent_object != nullptr, "Tried to synchronize an animation with neither an attached object, nor a valid special object mode."); if (special_mode == 0) { if (parent_object == nullptr) return; netsig_to_send = parent_object->net_signature; } BUILD_HEADER(ANIMATION_TRIGGERED); ADD_UINT(animationId); ADD_USHORT(netsig_to_send); ADD_USHORT(special_mode); // Currently empty. Reserved to find special pmi's for non-object animations ubyte metadata = (direction == animation::ModelAnimationDirection::RWD ? animation_direction_bit : 0) | (force ? animation_forced_bit : 0) | (instant ? animation_instant_bit : 0) | ( pause ? animation_pause_bit : 0); ADD_DATA(metadata); int actualTimestamp = 0; //If animation desync becomes a problem, send the ping-delay for animation rollback ADD_INT(actualTimestamp); // if I'm the server, send to everyone, else send to the server to be rebroadcasted if (Net_player->flags & NETINFO_FLAG_AM_MASTER) { multi_io_send_to_all_reliable(data, packet_size); } else { multi_io_send_reliable(Net_player, data, packet_size); } } void process_animation_triggered_packet(ubyte* data, header* hinfo) { int offset; // linked; unsigned int animationId; ushort netsig, special_mode; ubyte metadata; int time; // read all packet info offset = HEADER_LENGTH; GET_UINT(animationId); GET_USHORT(netsig); GET_USHORT(special_mode); GET_DATA(metadata); GET_INT(time); PACKET_SET_SIZE(); bool forced, instant, pause; animation::ModelAnimationDirection direction; direction = (metadata & (animation_direction_bit)) ? animation::ModelAnimationDirection::RWD : animation::ModelAnimationDirection::FWD; forced = metadata & (animation_forced_bit); instant = metadata & (animation_instant_bit); pause = metadata & (animation_pause_bit); float delay = time * 0.001f; object* objp = multi_get_network_object(netsig); if (objp == nullptr && special_mode == 0) //This means there is neither a good object, nor a known special mode. Don't even send the animation trigger on, just abort. return; auto animation = animation::ModelAnimationSet::s_animationById.find(animationId); if (animation != animation::ModelAnimationSet::s_animationById.end()) { if (special_mode == 0) { //with the above exit condition, this guarantees a non-null objp int model_instance_num = object_get_model_instance_num(objp); if (model_instance_num >= 0) animation->second->start(model_get_instance(model_instance_num), direction, forced, instant, pause, &delay); } else { //Currently empty. Reserved to find special pmi's for non-object animations } } //Need to broadcast back to other clients if (Net_player->flags & NETINFO_FLAG_AM_MASTER) { send_animation_triggered_packet(animationId, objp, special_mode, direction, forced, instant, pause, &time); } } void send_NEW_countermeasure_fired_packet(object *objp, int cmeasure_count, int rand_val) { ubyte data[MAX_PACKET_SIZE]; int packet_size; int np_index; net_player *ignore = NULL; // if i'm a multiplayer client, I should never send primary fired packets for anyone except me if(MULTIPLAYER_CLIENT && (Player_obj != objp)){ return; } Assert ( cmeasure_count < UCHAR_MAX ); BUILD_HEADER(COUNTERMEASURE_NEW); ADD_USHORT( objp->net_signature ); ADD_INT( rand_val ); nprintf(("Network","Sending NEW countermeasure packet!\n")); // determine if its a player if(MULTIPLAYER_MASTER){ np_index = multi_find_player_by_net_signature(objp->net_signature); if((np_index >= 0) && (np_index < MAX_PLAYERS)){ ignore = &Net_players[np_index]; } } // if I'm the server, send to all players if(MULTIPLAYER_MASTER){ multi_io_send_to_all(data, packet_size, ignore); } // otherwise send to the server else { multi_io_send(Net_player, data, packet_size); } } void process_NEW_countermeasure_fired_packet(ubyte *data, header *hinfo) { int offset; ushort signature; int rand_val; object *objp; offset = HEADER_LENGTH; GET_USHORT( signature ); GET_INT( rand_val ); PACKET_SET_SIZE(); objp = multi_get_network_object( signature ); if ( objp == NULL ) { nprintf(("network", "Could find object whose countermeasures are being launched!!!\n")); return; } if(objp->type != OBJ_SHIP){ return; } // if we're in client firing mode, ignore ones for myself if((Player_obj != NULL) && (Player_obj == objp)){ return; } if ( (rand_val >= NPERM_SIG_MIN) && (rand_val <= NPERM_SIG_MAX) ) { multi_set_network_signature((ushort)rand_val, MULTI_SIG_NON_PERMANENT); } // make it so ship can fire right away! Ships[objp->instance].cmeasure_fire_stamp = timestamp(0); if ( objp == Player_obj ){ nprintf(("network", "firing countermeasure from my ship\n")); } ship_launch_countermeasure( objp, rand_val ); } void send_beam_fired_packet(const beam_fire_info *fire_info, const beam_info *override) { ubyte data[MAX_PACKET_SIZE], val; int packet_size = 0; short u_beam_info; int shooter_subsys_index = -1, target_subsys_index = -1; ushort target_sig, shooter_sig; // only the server should ever be doing this Assert(MULTIPLAYER_MASTER); // setup outgoing data Assert(override != nullptr); if (override == nullptr) { return; } if (fire_info->bfi_flags & BFIF_IS_FIGHTER_BEAM) { Assertion(fire_info->bank >= 0, "Bank for fighter BEAM is invalid!"); Assertion(fire_info->point >= 0, "Bank point for fighter BEAM is invalid!"); if ( (fire_info->bank < 0) || (fire_info->point < 0) ) { return; } } else if (fire_info->shooter && fire_info->turret) { shooter_subsys_index = ship_get_subsys_index(fire_info->turret); Assertion(shooter_subsys_index >= 0, "BEAM fired from unknown subsystem!"); Assertion(shooter_subsys_index < SHRT_MAX, "BEAM fired from a subsystem beyond max limits!"); if (shooter_subsys_index < 0) { return; } } shooter_sig = (fire_info->shooter) ? fire_info->shooter->net_signature : 0; target_sig = (fire_info->target) ? fire_info->target->net_signature : 0; if (fire_info->target && fire_info->target_subsys) { target_subsys_index = ship_get_subsys_index(fire_info->target_subsys); } u_beam_info = static_cast(fire_info->beam_info_index); // build the header BUILD_HEADER(BEAM_FIRED); ADD_USHORT(shooter_sig); ADD_USHORT(target_sig); ADD_SHORT(u_beam_info); ADD_INT(fire_info->bfi_flags); ADD_SHORT(static_cast(shooter_subsys_index)); ADD_SHORT(static_cast(target_subsys_index)); val = static_cast(fire_info->fire_method); ADD_DATA(val); if (fire_info->bfi_flags & BFIF_IS_FIGHTER_BEAM) { ADD_SHORT(static_cast(fire_info->bank)); ADD_SHORT(static_cast(fire_info->point)); } // override info { // dir_a ADD_FLOAT(override->dir_a.xyz.x); ADD_FLOAT(override->dir_a.xyz.y); ADD_FLOAT(override->dir_a.xyz.z); // dir_b ADD_FLOAT(override->dir_b.xyz.x); ADD_FLOAT(override->dir_b.xyz.y); ADD_FLOAT(override->dir_b.xyz.z); // rot_axis ADD_FLOAT(override->rot_axis.xyz.x); ADD_FLOAT(override->rot_axis.xyz.y); ADD_FLOAT(override->rot_axis.xyz.z); ADD_DATA(override->shot_count); for (auto i = 0; i < override->shot_count; i++) { ADD_FLOAT(override->shot_aim[i]); } } if (fire_info->bfi_flags & BFIF_TARGETING_COORDS) { // target 1 ADD_FLOAT(fire_info->target_pos1.xyz.x); ADD_FLOAT(fire_info->target_pos1.xyz.y); ADD_FLOAT(fire_info->target_pos1.xyz.z); // target 2 ADD_FLOAT(fire_info->target_pos2.xyz.x); ADD_FLOAT(fire_info->target_pos2.xyz.y); ADD_FLOAT(fire_info->target_pos2.xyz.z); } if (fire_info->bfi_flags & BFIF_FLOATING_BEAM) { ADD_FLOAT(fire_info->starting_pos.xyz.x); ADD_FLOAT(fire_info->starting_pos.xyz.y); ADD_FLOAT(fire_info->starting_pos.xyz.z); ADD_DATA(fire_info->team); } // send to all clients multi_io_send_to_all_reliable(data, packet_size); } void process_beam_fired_packet(ubyte *data, header *hinfo) { int i, offset; ushort shooter_sig, target_sig; short shooter_subsys_index, target_subsys_index; short u_beam_info; beam_info b_info; beam_fire_info fire_info; float shot_aim; short bank = -1, point = -1; ubyte val; // only clients should ever get this Assert(MULTIPLAYER_CLIENT); memset(&fire_info, 0, sizeof(beam_fire_info)); // read in packet data offset = HEADER_LENGTH; GET_USHORT(shooter_sig); GET_USHORT(target_sig); GET_SHORT(u_beam_info); GET_INT(fire_info.bfi_flags); GET_SHORT(shooter_subsys_index); GET_SHORT(target_subsys_index); GET_DATA(val); fire_info.fire_method = val; if (fire_info.bfi_flags & BFIF_IS_FIGHTER_BEAM) { GET_SHORT(bank); GET_SHORT(point); } // override info { // dir_a GET_FLOAT(b_info.dir_a.xyz.x); GET_FLOAT(b_info.dir_a.xyz.y); GET_FLOAT(b_info.dir_a.xyz.z); // dir_b GET_FLOAT(b_info.dir_b.xyz.x); GET_FLOAT(b_info.dir_b.xyz.y); GET_FLOAT(b_info.dir_b.xyz.z); // rot_axis GET_FLOAT(b_info.rot_axis.xyz.x); GET_FLOAT(b_info.rot_axis.xyz.y); GET_FLOAT(b_info.rot_axis.xyz.z); GET_DATA(b_info.shot_count); for (i = 0; i < b_info.shot_count; i++) { GET_FLOAT(shot_aim); if (i < MAX_BEAM_SHOTS) { b_info.shot_aim[i] = shot_aim; } } fire_info.beam_info_override = &b_info; } if (fire_info.bfi_flags & BFIF_TARGETING_COORDS) { // target 1 GET_FLOAT(fire_info.target_pos1.xyz.x); GET_FLOAT(fire_info.target_pos1.xyz.y); GET_FLOAT(fire_info.target_pos1.xyz.z); // target 2 GET_FLOAT(fire_info.target_pos2.xyz.x); GET_FLOAT(fire_info.target_pos2.xyz.y); GET_FLOAT(fire_info.target_pos2.xyz.z); } if (fire_info.bfi_flags & BFIF_FLOATING_BEAM) { GET_FLOAT(fire_info.starting_pos.xyz.x); GET_FLOAT(fire_info.starting_pos.xyz.y); GET_FLOAT(fire_info.starting_pos.xyz.z); GET_DATA(fire_info.team); } PACKET_SET_SIZE(); fire_info.beam_info_index = u_beam_info; fire_info.shooter = multi_get_network_object(shooter_sig); fire_info.target = multi_get_network_object(target_sig); fire_info.burst_index = 0; // the weapon class arrives over the network, so validate it before using it to index Weapon_info if (!Weapon_info.in_bounds(fire_info.beam_info_index)) { nprintf(("Network", "Received invalid weapon class %d for BEAM weapon!\n", fire_info.beam_info_index)); return; } if ( fire_info.target && (target_subsys_index >= 0) ) { ship *targetp = &Ships[fire_info.target->instance]; fire_info.target_subsys = ship_get_indexed_subsys(targetp, target_subsys_index); } if ( fire_info.shooter && (fire_info.shooter->type == OBJ_SHIP) ) { ship *shipp = &Ships[fire_info.shooter->instance]; if (fire_info.bfi_flags & BFIF_IS_FIGHTER_BEAM) { Assertion(bank >= 0, "Fighter BEAM bank is invalid!"); Assertion(point >= 0, "Fighter BEAM point is invalid!"); polymodel *pm = model_get( Ship_info[shipp->ship_info_index].model_num ); // the bank and point also arrive over the network, so validate them against the model if ( (bank < 0) || (bank >= pm->n_guns) || (point < 0) || (point >= pm->gun_banks[bank].num_slots) ) { nprintf(("Network", "Couldn't get firing point for fighter BEAM weapon!\n")); return; } float field_of_fire = Weapon_info[fire_info.beam_info_index].field_of_fire; fire_info.local_fire_postion = pm->gun_banks[bank].pnt[point]; shipp->beam_sys_info.turret_norm.xyz.x = 0.0f; shipp->beam_sys_info.turret_norm.xyz.y = 0.0f; shipp->beam_sys_info.turret_norm.xyz.z = 1.0f; shipp->beam_sys_info.model_num = Ship_info[shipp->ship_info_index].model_num; shipp->beam_sys_info.turret_gun_sobj = pm->detail[0]; shipp->beam_sys_info.turret_num_firing_points = 1; shipp->beam_sys_info.turret_fov = cosf(fl_radians((field_of_fire != 0.0f) ? field_of_fire : 180.0f) / 2.0f); shipp->beam_sys_info.pnt = fire_info.local_fire_postion; shipp->beam_sys_info.turret_firing_point[0] = fire_info.local_fire_postion; shipp->fighter_beam_turret_data.disruption_timestamp = timestamp(0); shipp->fighter_beam_turret_data.turret_next_fire_pos = 0; shipp->fighter_beam_turret_data.current_hits = 1.0; shipp->fighter_beam_turret_data.system_info = &shipp->beam_sys_info; fire_info.turret = &shipp->fighter_beam_turret_data; fire_info.bank = bank; fire_info.point = point; } else if (shooter_subsys_index >= 0) { fire_info.turret = ship_get_indexed_subsys(shipp, shooter_subsys_index); } } // fire da beam beam_fire(&fire_info); } void send_sw_query_packet(ubyte code, char *txt) { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; // build the packet and add the code BUILD_HEADER(SW_STD_QUERY); ADD_DATA(code); if((code == SW_STD_START) || (code == SW_STD_BAD)){ ADD_STRING(txt); } // if I'm the host, send to standalone if(MULTIPLAYER_HOST){ Assert(!MULTIPLAYER_MASTER); Assert(code == SW_STD_START); multi_io_send_reliable(Net_player, data, packet_size); } // otherwise standalone sends back to host else { Assert(Game_mode & GM_STANDALONE_SERVER); Assert(code != SW_STD_START); Assert(Netgame.host != NULL); if(Netgame.host != NULL){ multi_io_send_reliable(Netgame.host, data, packet_size); } } } void process_sw_query_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; ubyte code; char txt[MAX_SQUAD_RESPONSE_LEN+1]; GET_DATA(code); if ( (code == SW_STD_START) || (code == SW_STD_BAD) ) { GET_STRING(txt); } PACKET_SET_SIZE(); // to host from standalone if (MULTIPLAYER_HOST) { Assert( !MULTIPLAYER_MASTER ); if (code == SW_STD_OK) { Multi_sw_std_query = 1; } else { Assert(code == SW_STD_BAD); SDL_strlcpy(Multi_sw_bad_reply, txt, SDL_arraysize(Multi_sw_bad_reply)); Multi_sw_std_query = 0; } } // to standalone from host else { Assert(Game_mode & GM_STANDALONE_SERVER); Assert(code == SW_STD_START); multi_sw_std_query(txt); } } void send_event_update_packet(int event) { ubyte data[MAX_PACKET_SIZE]; ushort u_event = (ushort)event; int packet_size = 0; // build the header and add the event BUILD_HEADER(EVENT_UPDATE); ADD_USHORT(u_event); ADD_INT(Mission_events[event].flags); ADD_INT(Mission_events[event].formula); ADD_INT(Mission_events[event].result); ADD_INT(Mission_events[event].count); // send to all players multi_io_send_to_all_reliable(data, packet_size); } void process_event_update_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; int store_flags; ushort u_event; // get the data GET_USHORT(u_event); store_flags = Mission_events[u_event].flags; GET_INT(Mission_events[u_event].flags); GET_INT(Mission_events[u_event].formula); GET_INT(Mission_events[u_event].result); GET_INT(Mission_events[u_event].count); PACKET_SET_SIZE(); // went from non directive special to directive special if(!(store_flags & MEF_DIRECTIVE_SPECIAL) && (Mission_events[u_event].flags & MEF_DIRECTIVE_SPECIAL)){ mission_event_set_directive_special(u_event); } // if we went directive special to non directive special else if((store_flags & MEF_DIRECTIVE_SPECIAL) && !(Mission_events[u_event].flags & MEF_DIRECTIVE_SPECIAL)){ mission_event_unset_directive_special(u_event); } if (Mission_events[u_event].result && !Mission_events[u_event].satisfied_time.isValid()) { Mission_events[u_event].satisfied_time = _timestamp(); if ( !Mission_events[u_event].objective_text.empty() ) { mission_event_set_completion_sound_timestamp(); } } } // Karajorma - Sends a packet to all clients telling them that a SEXP variable has changed its value void send_variable_update_packet(int variable_index, char *value) { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; if(Net_player == NULL){ return; } if(!(Net_player->flags & NETINFO_FLAG_AM_MASTER)){ return; } // build the packet and add the data BUILD_HEADER(VARIABLE_UPDATE); ADD_INT(variable_index); ADD_STRING(value); if (MULTIPLAYER_MASTER) { // send to all players multi_io_send_to_all_reliable(data, packet_size); } else { multi_io_send_reliable(Net_player, data, packet_size); } } void process_variable_update_packet( ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; char value[TOKEN_LENGTH]; int variable_index; // get the data GET_INT(variable_index); GET_STRING(value); PACKET_SET_SIZE(); // set the sexp_variable if ( (variable_index >= 0) && (variable_index < sexp_variable_count()) ) { strcpy_s(Sexp_variables[variable_index].text, value); } // send the packet on to all clients. if (MULTIPLAYER_MASTER) { send_variable_update_packet(variable_index, Sexp_variables[variable_index].text); } } // weapon detonate packet void send_weapon_detonate_packet(object *objp) { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; // sanity checks Assert(MULTIPLAYER_MASTER); if(!MULTIPLAYER_MASTER){ return; } Assert(objp != NULL); if(objp == NULL){ return; } // build the header and add the data BUILD_HEADER(WEAPON_DET); ADD_USHORT(objp->net_signature); // send to all players multi_io_send_to_all(data, packet_size); } void process_weapon_detonate_packet(ubyte *data, header *hinfo) { ushort net_sig; int offset = HEADER_LENGTH; object *objp = NULL; // get the weapon signature GET_USHORT(net_sig); PACKET_SET_SIZE(); // lookup the weapon objp = multi_get_network_object(net_sig); if((objp != NULL) && (objp->type == OBJ_WEAPON) && (objp->instance >= 0)){ weapon_detonate(objp); } } // flak fired packet void send_flak_fired_packet(int ship_objnum, int subsys_index, int weapon_objnum, float flak_range, float dist_to_target, float target_radius, float target_forward_speed) { int packet_size; ushort pnet_signature; ubyte data[MAX_PACKET_SIZE]; object *objp; ship_subsys *ssp; // sanity if((weapon_objnum < 0) || (Objects[weapon_objnum].type != OBJ_WEAPON) || (Objects[weapon_objnum].instance < 0) || (Weapons[Objects[weapon_objnum].instance].weapon_info_index < 0)){ return; } // local setup -- be sure we are actually passing a weapon!!!! objp = &Objects[weapon_objnum]; Assert ( objp->type == OBJ_WEAPON ); pnet_signature = Objects[ship_objnum].net_signature; ssp = ship_get_indexed_subsys( &Ships[Objects[ship_objnum].instance], subsys_index ); if(ssp == NULL){ return; } // build the fire turret packet. BUILD_HEADER(FLAK_FIRED); packet_size += multi_pack_unpack_position(1, data + packet_size, &objp->orient.vec.fvec); ADD_USHORT( pnet_signature ); ADD_SHORT( static_cast(Weapons[Objects[weapon_objnum].instance].weapon_info_index) ); ADD_SHORT( static_cast(subsys_index) ); // where we might accidentally dereference nullptrs, send 0 instead constexpr float ZERO_VALUE = 0.0f; // ensure a nullptr is not dereferenced for the next two values. if (ssp->submodel_instance_1 != nullptr) { ADD_FLOAT( ssp->submodel_instance_1->cur_angle ); } else { ADD_FLOAT( ZERO_VALUE ); } if (ssp->submodel_instance_2 != nullptr) { ADD_FLOAT( ssp->submodel_instance_2->cur_angle ); } else { ADD_FLOAT(ZERO_VALUE); } ADD_FLOAT( flak_range ); ADD_FLOAT( dist_to_target ); ADD_FLOAT( target_radius ); ADD_FLOAT( target_forward_speed ); multi_io_send_to_all(data, packet_size); multi_rate_add(1, "flk", packet_size); } void process_flak_fired_packet(ubyte *data, header *hinfo) { int offset, weapon_objnum; ushort pnet_signature; vec3d pos, dir; matrix orient; vec3d o_fvec; short turret_index, wid; object *objp; ship_subsys *ssp; ship *shipp; float angle1, angle2; float flak_range; float dist_to_target; float target_radius; float target_forward_speed; // get the data for the turret fired packet offset = HEADER_LENGTH; offset += multi_pack_unpack_position(0, data + offset, &o_fvec); GET_USHORT( pnet_signature ); GET_SHORT( wid ); GET_SHORT( turret_index ); GET_FLOAT( angle1 ); GET_FLOAT( angle2 ); GET_FLOAT( flak_range ); GET_FLOAT( dist_to_target ); GET_FLOAT( target_radius ); GET_FLOAT( target_forward_speed ); PACKET_SET_SIZE(); // move our counter forward the number of bytes we have read // if we don't have a valid weapon index then bail if ( (wid < 0) || (wid >= weapon_info_size()) || !(Weapon_info[wid].wi_flags[Weapon::Info_Flags::Flak]) ) { return; } // find the object objp = multi_get_network_object( pnet_signature ); if ( objp == NULL ) { nprintf(("network", "could find parent object with net signature %d for flak firing\n", pnet_signature)); return; } // if this isn't a ship, do nothing if ( objp->type != OBJ_SHIP ){ return; } // make an orientation matrix from the o_fvec // NOTE: o_fvec is NOT normalized due to pack/unpack altering values! vm_vector_2_matrix(&orient, &o_fvec, nullptr, nullptr); // find this turret, and set the position of the turret that just fired to be where it fired. Quite a // hack, but should be suitable. shipp = &Ships[objp->instance]; ssp = ship_get_indexed_subsys( shipp, turret_index ); if(ssp == NULL){ return; } // bash the position and orientation of the turret, if it's not a nulltpr if (ssp->submodel_instance_1 != nullptr) { ssp->submodel_instance_1->cur_angle = angle1; } if (ssp->submodel_instance_2 != nullptr) { ssp->submodel_instance_2->cur_angle = angle2; } // get the world position of the weapon ship_get_global_turret_info(objp, ssp->system_info, &pos, &dir); auto launch_curve_data = WeaponLaunchCurveData { ssp->system_info->turret_num_firing_points, dist_to_target, target_radius, target_forward_speed, }; // create the weapon object weapon_objnum = weapon_create( &pos, &orient, wid, OBJ_INDEX(objp), -1, true, false, nullptr, -1, -1, launch_curve_data); if (weapon_objnum != -1) { const weapon_info& wip = Weapon_info[wid]; if ( wip.launch_snd.isValid() ) { snd_play_3d( gamesnd_get_game_sound(Weapon_info[wid].launch_snd), &pos, &View_position ); } object& wp_obj = Objects[weapon_objnum]; const weapon& wp = Weapons[wp_obj.instance]; if (wip.muzzle_effect.isValid()) { float radius_mult = 1.f; if (wip.render_type == WRT_LASER) { radius_mult = wip.weapon_curves.get_output(weapon_info::WeaponCurveOutputs::LASER_RADIUS_MULT, wp, &wp.modular_curves_instance); } //spawn particle effect auto particleSource = particle::ParticleManager::get()->createSource(wip.muzzle_effect); //This could potentially be attached to the ship, but might look weird if the spawn position of the weapon is ever interpolated away from the ship's barrel. particleSource->setHost(std::make_unique(pos, orient, objp->phys_info.vel)); particleSource->setTriggerRadius(wp_obj.radius * radius_mult); particleSource->setTriggerVelocity(vm_vec_mag_quick(&wp_obj.phys_info.vel)); particleSource->finishCreation(); } // set its range explicitly - make it long enough so that it's guaranteed to still exist when the server tells us it blew up flak_set_range(&wp_obj, (float)flak_range); } } #define ADD_NORM_VEC(d) do { Assert((packet_size + 3) < MAX_PACKET_SIZE); char vnorm[3] = { (char)(d.x * 127.0f), (char)(d.y * 127.0f), (char)(d.z * 127.0f) }; memcpy(data + packet_size, vnorm, 3); packet_size += 3; } while(false); #define GET_NORM_VEC(d) do { char vnorm[3]; memcpy(vnorm, data+offset, 3); d.x = (float)vnorm[0] / 127.0f; d.y = (float)vnorm[1] / 127.0f; d.z = (float)vnorm[2] / 127.0f; } while(false); // player pain packet void send_player_pain_packet(net_player *pl, int weapon_info_index, float damage, const vec3d *force, const vec3d *hitpos, int quadrant_num) { ubyte data[MAX_PACKET_SIZE]; short windex; ushort udamage; int packet_size = 0; Assert(MULTIPLAYER_MASTER); if(!MULTIPLAYER_MASTER){ return; } Assert(pl != NULL); if(pl == NULL){ return; } // build the packet and add the code BUILD_HEADER(NETPLAYER_PAIN); windex = (short)weapon_info_index; ADD_SHORT(windex); udamage = (ushort)damage; ADD_USHORT(udamage); ADD_VECTOR((*force)); ADD_VECTOR((*hitpos)); ADD_INT(quadrant_num); // send to the player multi_io_send(pl, data, packet_size); multi_rate_add(1, "pai", packet_size); } void process_player_pain_packet(const ubyte *data, header *hinfo) { int offset; short windex = 0; ushort udamage; vec3d force; vec3d hit_pos; weapon_info *wip; int quadrant_num; // get the data for the pain packet offset = HEADER_LENGTH; GET_SHORT(windex); GET_USHORT(udamage); GET_VECTOR(force); GET_VECTOR(hit_pos); GET_INT(quadrant_num); PACKET_SET_SIZE(); // mprintf(("PAIN!\n")); // get weapon info pointer Assert((windex < weapon_info_size()) && (Weapon_info[windex].subtype == WP_LASER)); if(! ((windex < weapon_info_size()) && (Weapon_info[windex].subtype == WP_LASER)) ){ return; } wip = &Weapon_info[windex]; // play the weapon hit sound Assert(Player_obj != NULL); if(Player_obj == NULL){ return; } //Assume the weapon is armed -WMC weapon_hit_do_sound(Player_obj, wip, &Player_obj->pos, true, quadrant_num); // we need to do 3 things here. player pain (game flash), weapon hit sound, ship_apply_whack() ship_hit_pain((float)udamage, quadrant_num); // apply the whack ship_apply_whack(&force, &hit_pos, Player_obj); } // lightning packet void send_lightning_packet(int bolt_type_internal, vec3d *start, vec3d *strike) { ubyte data[MAX_PACKET_SIZE]; char val; int packet_size = 0; // build the header and add the data BUILD_HEADER(LIGHTNING_PACKET); val = (char)bolt_type_internal; ADD_DATA(val); ADD_VECTOR((*start)); ADD_VECTOR((*strike)); // send to everyone unreliable for now multi_io_send_to_all(data, packet_size); } void process_lightning_packet(ubyte *data, header *hinfo) { int offset; char bolt_type_internal; vec3d start, strike; // read the data offset = HEADER_LENGTH; GET_DATA(bolt_type_internal); GET_VECTOR(start); GET_VECTOR(strike); PACKET_SET_SIZE(); // invalid bolt? if(bolt_type_internal < 0){ return; } // fire it up nebl_bolt(bolt_type_internal, &start, &strike); } void send_bytes_recvd_packet(net_player *pl) { // only clients should ever be doing this if(pl == NULL){ return; } ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; BUILD_HEADER(BYTES_SENT); ADD_INT(pl->cl_bytes_recvd); // send to the server multi_io_send_reliable(pl, data, packet_size); } void process_bytes_recvd_packet(ubyte *data, header *hinfo) { int bytes; int pid; net_player *pl = NULL; int offset = HEADER_LENGTH; GET_INT(bytes); PACKET_SET_SIZE(); // not server? if(Net_player == NULL){ return; } if(!MULTIPLAYER_MASTER){ return; } // make sure we know what player sent this pid = find_player_index(hinfo->id); if((pid < 0) || (pid >= MAX_PLAYERS)){ return; } pl = &Net_players[pid]; // compute his pl pl->cl_bytes_recvd = bytes; if(bytes < 0){ return; } pl->sv_last_pl = (int)(100.0f * (1.0f - ((float)pl->cl_bytes_recvd / (float)pl->sv_bytes_sent))); // reset bytes sent pl->sv_bytes_sent = 0; } // host transfer void send_host_captain_change_packet(short player_id, bool captain_change) { ubyte data[MAX_PACKET_SIZE], val; int packet_size = 0; // build the packet BUILD_HEADER(TRANSFER_HOST); ADD_SHORT(player_id); val = captain_change ? 1 : 0; ADD_DATA(val); // send to all multi_io_send_to_all_reliable(data, packet_size); } void process_host_captain_change_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; int idx, found_player; short player_id; ubyte captain_change; // get the player id GET_SHORT(player_id); GET_DATA(captain_change); PACKET_SET_SIZE(); // captain change if(captain_change){ // flag the new guy for(idx=0; idxcallsign, Net_players[idx].p_info.team + 1); break; } } } else { // unflag all old players for(idx=0; idxcallsign); } found_player = 1; break; } } // doh if(!found_player){ multi_quit_game(PROMPT_NONE, MULTI_END_NOTIFY_NONE, MULTI_END_ERROR_HOST_LEFT); } } } void send_self_destruct_packet() { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; // bogus if(Net_player == NULL){ return; } // if i'm the server, I shouldn't be here Assert(!(Net_player->flags & NETINFO_FLAG_AM_MASTER)); if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ return; } // only if this is valid if(MULTI_OBSERVER(Net_players[MY_NET_PLAYER_NUM])){ return; } // bogus object? if((Player_ship == NULL) || (Player_obj == NULL)){ return; } // self destruct BUILD_HEADER(SELF_DESTRUCT); ADD_USHORT(Player_obj->net_signature); // send to the server multi_io_send_reliable(Net_player, data, packet_size); } void process_self_destruct_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; ushort net_sig; int np_index; // get the net signature GET_USHORT(net_sig); PACKET_SET_SIZE(); // get the player np_index = find_player_index(hinfo->id); if(np_index < 0){ return; } if(MULTI_OBSERVER(Net_players[np_index])){ return; } if(Net_players[np_index].m_player == NULL){ return; } if((Net_players[np_index].m_player->objnum < 0) || (Net_players[np_index].m_player->objnum >= MAX_OBJECTS)){ return; } if(Objects[Net_players[np_index].m_player->objnum].net_signature != net_sig){ return; } if(Objects[Net_players[np_index].m_player->objnum].type != OBJ_SHIP){ return; } if((Objects[Net_players[np_index].m_player->objnum].instance < 0) || (Objects[Net_players[np_index].m_player->objnum].instance >= MAX_SHIPS)){ return; } // do eet ship_self_destruct(&Objects[Net_players[np_index].m_player->objnum]); } void send_sexp_packet(ubyte *sexp_packet, int num_ubytes) { ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; Assert (MULTIPLAYER_MASTER); // must have a bare minimum of OP, COUNT and TERMINATOR if (num_ubytes < MIN_SEXP_PACKET_SIZE) { Warning(LOCATION, "Invalid call to send_sexp_packet. Not enough data included!"); return; } const int max_size = MAX_PACKET_SIZE - HEADER_LENGTH - static_cast(sizeof(ushort)); if (num_ubytes > max_size) { Warning(LOCATION, "Invalid call to send_sexp_packet. Too much data included!"); return; } BUILD_HEADER(SEXP); ADD_USHORT(static_cast(num_ubytes)); ADD_DATA_BLOCK(sexp_packet, num_ubytes); // send to all multi_io_send_to_all_reliable(data, packet_size); } void process_sexp_packet(ubyte *data, header *hinfo) { int offset = HEADER_LENGTH; ushort num_ubytes; ubyte received_packet[MAX_PACKET_SIZE]; GET_USHORT(num_ubytes); GET_DATA_BLOCK(received_packet, num_ubytes); PACKET_SET_SIZE(); sexp_packet_received(received_packet, num_ubytes); } // sends as many turrets as possible, and returns those it can not yet send because of packet size. SCP_vector send_turret_tracking_packet(ushort parent_sig, SCP_vector& subsys_indexes) { Assertion(MULTIPLAYER_MASTER, "A non-server is trying to send a turret tracking packet. This is a coder mistake, please report!"); // Max size, minus some bytes for easy handling and safety. constexpr int MAX_TURRET_PACKET_SIZE = MAX_PACKET_SIZE - 20; SCP_vector overflow_list; Assertion (parent_sig != 0, "The turret tracking packet manager is trying to send a packet with a net signature of 0. This is nonsense and a coder mistake, please report!"); // somehow trying to send for an an invalid or non-network object if (parent_sig == 0) { // returning an empty list will signal the manager to clear its entries return overflow_list; } auto temp = multi_get_network_object(parent_sig); // this means that the ship may have been destroyed before being able to send the packet. Ignore. if (temp == nullptr){ // returning an empty list will signal the manager to clear its entries return overflow_list; } ship* shipp = &Ships[temp->instance]; // Required for all send functions ubyte data[MAX_PACKET_SIZE]; int packet_size = 0; BUILD_HEADER(TURRET_TRACK); ubyte last_index = 0; // this is not usually done, but if the number of subsystems actually ends up being different than the expected, // this ubyte in the packet will get overwritten at the end of this function. ADD_DATA(last_index); // send the time that this happened. int time_out = Multi_Timing_Info.get_current_time(); ADD_INT(time_out); ADD_USHORT(parent_sig); ubyte count = 0; // no, I don't expect to send UINT turrets, but I don't want to be stuck // in an infinite loop with a smaller word size. for (uint i = 0; i < subsys_indexes.size(); i++){ // pre count is now being switched to a limiter to ensure that we do not exceed MAX_TURRET_PACKET_SIZE if (packet_size < MAX_TURRET_PACKET_SIZE) { auto ssp = ship_get_indexed_subsys( shipp, subsys_indexes[i]); // make sure the subsytem is valid. If not, just continue. if (ssp != nullptr){ // Add the data for the valid entry ADD_SHORT( static_cast(subsys_indexes[i])); ushort target_netsig = (ssp->turret_enemy_objnum > -1) ? Objects[ssp->turret_enemy_objnum].net_signature : 0; ADD_USHORT(target_netsig); packet_size += multi_pack_turret_angles(data + packet_size, ssp); ++count; } else { // if we don't have a valid subsystem. ++last_index; } } else { overflow_list.push_back(subsys_indexes[i]); } } // nothing to send means that we should not send the packet, and just send back the overflow list. if (count < 1) { return overflow_list; } // and now, actually place the number of turrets we are updating // this is a little hacky, but it's best to work through the serialization macros to rewrite the data. int packet_size_record = packet_size; packet_size = HEADER_LENGTH; ADD_DATA(count); packet_size = packet_size_record; // The packet is now finished, so send. multi_io_send_to_all(data, packet_size); return overflow_list; } void process_turret_tracking_packet(ubyte *data, header *hinfo) { Assertion(MULTIPLAYER_CLIENT, "A non-client is receiving a turret tracking packet. This is a coder mistake, please report!"); int offset = HEADER_LENGTH; int time_in; ushort parent_netsig; ubyte last_index; GET_DATA(last_index); GET_INT(time_in); GET_USHORT(parent_netsig); auto parent = multi_get_network_object(parent_netsig); std::pair angle1; std::pair angle2; angle1.first = false; angle1.second = 0.0f; angle2.first = false; angle2.second = 0.0f; for (ubyte index = 0; index < last_index; index++){ short subsystem_index; ushort target_netsig; GET_SHORT(subsystem_index); GET_USHORT(target_netsig); offset += multi_unpack_turret_angles(data + offset, angle1, angle2); // even if the ship does not exist if (parent != nullptr){ Multi_Turret_Manager.add_incoming_packet(time_in, OBJ_INDEX(parent), subsystem_index, target_netsig, angle1, angle2); } angle1.first = false; angle2.first = false; } PACKET_SET_SIZE(); }