Files
fs2open.github.com/code/network/multimsgs.cpp
T
Kestrellius 6921f550c1 Enhanced functionality for conditional impacts (#6785)
* add new conditions

* don't worry about it

* implement new conditions

* bugfixing

* handle pokethrough differently

* clang for the clang god

* appeasement

* to the last i grapple with thee

* from hell's heart etc

* bugfixing

* support hits while dying

* fix shield math, cleanup

* fix memory error
2025-07-12 11:40:53 +02:00

9291 lines
258 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include <climits>
#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<input_size ; i++ ) {
character = *inputbuf++;
index = find_child_node( string_code, character );
if ( dict[ index ].code_value != - 1 )
string_code = dict[ index ].code_value;
else {
dict[ index ].code_value = next_code++;
dict[ index ].parent_code = string_code;
dict[ index ].character = (char) character;
output_bits( &output, (unsigned long) string_code, current_code_bits );
string_code = character;
if ( next_code > 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<MAX_PLAYERS; idx++){
if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx])){
// force unreliable data
if(Net_players[idx].s_info.unreliable_buffer_size > 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;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && (&Net_players[idx] != from)){
multi_io_send_reliable(&Net_players[idx], data, packet_size);
}
}
break;
// message only friendly players
case MULTI_MSG_FRIENDLY:
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && (&Net_players[idx] != from) && (Net_players[idx].p_info.team == from->p_info.team)){
multi_io_send_reliable(&Net_players[idx], data, packet_size);
}
}
break;
// message only hostile players
case MULTI_MSG_HOSTILE:
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && (&Net_players[idx] != from) && (Net_players[idx].p_info.team != from->p_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;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && (&Net_players[idx] != from) && multi_msg_matches_expr(&Net_players[idx],expr) ){
multi_io_send_reliable(&Net_players[idx], data, packet_size);
undeliverable = false;
}
}
break;
}
// if the message can't be delivered, notify the player
if (undeliverable) {
switch(mode){
case MULTI_MSG_EXPR:
// if the message came from the server
if (from == Net_player) {
multi_display_chat_msg ("Unable to send message, player does not exist", 0, 0);
}
// otherwise send a message back to the player
else {
send_game_chat_packet(Net_player, "Unable to send message, player does not exist", MULTI_MSG_TARGET, from, NULL, 1);
}
break;
}
}
}
// send to the server, who will take care of routing it
else {
multi_io_send_reliable(Net_player, data, packet_size);
}
}
//+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
// process a general game chat packet, if we're the standalone we should rebroadcast
void process_game_chat_packet( ubyte *data, header *hinfo )
{
int offset;
ubyte mode, server_msg;
int color_index, player_index, to_player_index, should_display;
char msg[MULTI_MSG_MAX_TEXT_LEN+CALLSIGN_LEN+2];
char expr[255];
short from, to;
offset = HEADER_LENGTH;
// get the id of the sender
GET_SHORT(from);
// determine if this is a server message
GET_DATA(server_msg);
// get the mode
GET_DATA(mode);
// if targeting a specific player, get the address
to = -1;
switch(mode){
case MULTI_MSG_TARGET:
GET_SHORT(to);
break;
case MULTI_MSG_EXPR:
GET_STRING(expr);
break;
}
// get the message itself
GET_STRING(msg);
PACKET_SET_SIZE();
// get the index of the sending player
color_index = find_player_index(from);
player_index = color_index;
// if we couldn't find the player - bail
if(player_index == -1){
nprintf(("Network","Could not find player for processing game chat packet!\n"));
return;
}
should_display = 0;
// if we're the server, determine what to do with the packet here
if(Net_player->flags & 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<ubyte>(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; i<MAX_PLAYERS; i++) {
// skip non connected players
if ( !MULTI_CONNECTED(Net_players[i]) ){
continue;
}
// skip this new player's entry
if ( npp->player_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<ubyte>(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; i<MAX_PLAYERS; i++) {
// skip unconnected players as well as this new guy himself
if ( !MULTI_CONNECTED(Net_players[i]) || psnet_same(&Net_players[new_player_num].p_info.addr, &(Net_players[i].p_info.addr)) || (Net_player == &Net_players[i])) {
continue;
}
// send the new packet
send_new_player_packet(new_player_num,&Net_players[i]);
}
// 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);
}
// 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; idx<MAX_PLAYERS; idx++){
if(MULTI_CONNECTED(Net_players[idx]) && (Net_player != &Net_players[idx])){
send_netgame_descript_packet(&Net_players[idx].p_info.addr, 1);
}
}
} else {
multi_io_send_reliable(pl, data, packet_size);
send_netgame_descript_packet( &pl->p_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.
if (addr == nullptr) {
UNREACHABLE("Net address not specified!");
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;idx<MAX_PLAYERS;idx++){
// only send info for connected players
if(MULTI_CONNECTED(Net_players[idx])){
// add a stop byte
val = 0x0;
ADD_DATA(val);
// add the net player's information
ADD_SHORT(Net_players[idx].player_id);
ADD_INT(Net_players[idx].state);
ADD_INT(Net_players[idx].p_info.ship_class);
ADD_INT(Net_players[idx].tracker_player_id);
//Used to be a check for a CD here --Mjn
val = 1;
ADD_DATA(val);
}
}
// add the final stop byte
val = 0xff;
ADD_DATA(val);
// broadcast the packet
if(!(Game_mode & GM_IN_MISSION)){
if ( pl == NULL ) {
multi_io_send_to_all_reliable(data, packet_size);
} else {
multi_io_send_reliable(pl, data, packet_size);
}
} else {
if ( pl == NULL ) {
multi_io_send_to_all(data, packet_size);
} else {
multi_io_send(pl, data, packet_size);
}
}
} else {
// add a stop byte
val = 0x0;
ADD_DATA(val);
// add my current state in the netgame to this packet
ADD_SHORT(Net_player->player_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_VAPORIZED) {
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, int /*starting_count*/, 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;
ai_info *aip;
// 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;
}
}
aip = &Ai_info[shipp->ai_index];
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 ( aip->current_target_is_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 ( aip->target_objnum != -1) {
target_net_signature = Objects[aip->target_objnum].net_signature;
if ( (Objects[aip->target_objnum].type == OBJ_SHIP) && (aip->targeted_subsys != NULL) ) {
s_index = ship_get_subsys_index( aip->targeted_subsys );
}
if ( Objects[aip->target_objnum].type == OBJ_WEAPON ) {
Assert(Weapon_info[Weapons[Objects[aip->target_objnum].instance].weapon_info_index].wi_flags[Weapon::Info_Flags::Bomb]);
}
}
ADD_USHORT( target_net_signature );
ADD_SHORT( static_cast<short>(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;
}
// 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<short>(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 )
{
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<short>(Weapons[objp->instance].weapon_info_index) );
ADD_SHORT( static_cast<short>(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);
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;
// 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 );
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
vm_vector_2_matrix_norm(&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,
};
// 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, 0.0f, ssp, 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<LogType>(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;
}
}
// If the end_mission SEXP has been used tell clients to skip straight to the debrief screen
void send_force_end_mission_packet()
{
ubyte data[MAX_PACKET_SIZE];
int packet_size;
packet_size = 0;
BUILD_HEADER(FORCE_MISSION_END);
if (Net_player->flags & NETINFO_FLAG_AM_MASTER)
{
// tell everyone to leave the game
multi_io_send_to_all_reliable(data, packet_size);
}
}
// 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();
ml_string("Receiving force end mission packet");
// Since only the server sends out these packets it should never receive one
Assert (!(Net_player->flags & NETINFO_FLAG_AM_MASTER));
multi_handle_sudden_mission_end();
send_debrief_event();
}
// 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();
// 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;idx<MAX_PLAYERS;idx++){
if( MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_OBSERVER(Net_players[idx])){
ADD_DATA(stop);
ADD_SHORT(Net_players[idx].player_id);
ADD_USHORT(Objects[Net_players[idx].m_player->objnum].net_signature);
ADD_SHORT(static_cast<short>(Net_players[idx].p_info.ship_class));
val = static_cast<char>(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> - Client runs through the normal button_function procedure. Any remaining control bits are implied as being
// server critical.
// 2.) <client> - Client puts this button_info item into his last_buttons array and sends a bunch of SHIP_STATUS packets
// for added redundancy.
// 3.) <server> - 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> - 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.) <client> - 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<short>(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( (index >= 0) && (index < Num_reinforcements) );
hud_squadmsg_call_reinforcement(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)?"<none>":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)?"<None>":Ships[repaired_objp->instance].ship_name, (repair_objp==NULL)?"<None>":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<int>(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<ushort>(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;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && (&Net_players[idx] != Net_player) && (&Net_players[idx] != &Net_players[player_index]) ){
multi_io_send_reliable(&Net_players[idx], data, offset);
}
}
} else {
// set the proper pool pointers
common_set_team_pointers((int)team);
// read in the block of data, and apply it to the weapons/ship pools
offset += restore_wss_data(data + offset);
PACKET_SET_SIZE();
// set the pool pointers back to my own team
common_set_team_pointers(Net_player->p_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<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx])){
ADD_DATA(stop);
ADD_SHORT(Net_players[idx].player_id);
// break the p_info structure by member, so we don't overwrite any absolute pointers
// ADD_DATA(Net_players[idx].p_info);
ADD_INT(Net_players[idx].p_info.team);
val = static_cast<char>(Net_players[idx].p_info.ship_index);
ADD_DATA(val);
ADD_SHORT(static_cast<short>(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;idx<Multi_ts_num_deleted;idx++){
sval = (ushort)Objects[Multi_ts_deleted_objnums[idx]].net_signature;
ADD_USHORT(sval);
}
// ship count
ship_count = 0;
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;
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<short>(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<char>(shipp->weapons.num_primary_banks);
ADD_DATA(bval);
bval = static_cast<char>(shipp->weapons.num_secondary_banks);
ADD_DATA(bval);
// add weapon bank status
bval = static_cast<char>(shipp->weapons.current_primary_bank);
if (pl != nullptr) {
pl->s_info.cur_primary_bank = bval;
}
// Assert(bval != -1);
ADD_DATA(bval);
bval = static_cast<char>(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<short>(shipp->weapons.primary_bank_weapons[idx]));
}
// secondary weapon info
for (idx = 0; idx < shipp->weapons.num_secondary_banks; idx++) {
ADD_SHORT(static_cast<short>(shipp->weapons.secondary_bank_weapons[idx]));
ADD_SHORT(static_cast<short>(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;idx<Multi_ts_num_deleted;idx++){
// get the ship's objnum
GET_USHORT(sval);
objp = NULL;
objp = multi_get_network_object(sval);
if(objp != NULL){
// delete the ship appropriately
// mark the object as having been deleted
Multi_ts_deleted_objnums[idx] = OBJ_INDEX(objp);
// delete the ship
ship_add_exited_ship(&Ships[objp->instance], 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; idx<ship_count; idx++){
// get the object's net signature
GET_USHORT(net_sig);
GET_SHORT(sinfo_index);
GET_DATA(ts_index);
// attempt to get the object
objp = multi_get_network_object(net_sig);
// make sure we found a ship
Assertion(objp != NULL, "idx: %d, ship_count: %d, sinfo_index: %u, ts_index: %u, net_sig: %u",
idx, ship_count, sinfo_index, ts_index, net_sig);
Assertion(objp->type == 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; idx<ship_count; idx++){
// get the object's net signature
GET_USHORT(net_sig);
// attempt to get the object
objp = multi_get_network_object(net_sig);
// make sure we found a ship
Assert((objp != NULL) && (objp->type == 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<int>(b);
GET_DATA(b);
Assert( b != -1 );
shipp->weapons.num_secondary_banks = static_cast<int>(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<int>(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<int>(val_short);
GET_SHORT(val_short);
shipp->weapons.secondary_bank_ammo[j] = static_cast<int>(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;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && !MULTI_PERM_OBSERVER(Net_players[idx]) && (Net_players[idx].m_player->objnum != -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<MULTI_TS_NUM_SHIP_SLOTS;idx++){
// add the ship class
val_short = static_cast<short>(Wss_slots_teams[team_num][idx].ship_class);
ADD_SHORT(val_short);
// add the weapons and counts
for(i = 0;i<MAX_SHIP_WEAPONS;i++){
val_short = static_cast<short>(Wss_slots_teams[team_num][idx].wep[i]);
ADD_SHORT(val_short);
val_short = static_cast<short>(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<int>(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
idx = 0;
while (idx < MAX_SHIP_CLASSES) {
send_player_stats_block_packet(pl, STATS_ALLTIME_KILLS, target, idx, MAX_SHIP_CLASSES-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
idx = 0;
while (idx < MAX_SHIP_CLASSES) {
send_player_stats_block_packet(pl, STATS_MISSION_CLASS_KILLS, target, idx, MAX_SHIP_CLASSES-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; idx<MAX_PLAYERS; idx++){
u_tmp = (ushort)sc->m_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<ushort>(offset));
ADD_USHORT(static_cast<ushort>(len));
for (idx = offset; idx < 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<ushort>(offset));
ADD_USHORT(static_cast<ushort>(len));
for (idx = offset; idx < 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 (idx < MAX_SHIP_CLASSES) {
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 (idx < MAX_SHIP_CLASSES) {
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<ubyte>(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<ubyte>(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<ushort>(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<ubyte>(shipp->weapons.num_secondary_banks);
ADD_DATA(val);
for(i=0; i<shipp->weapons.num_secondary_banks; i++){
ADD_SHORT(static_cast<short>(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<ubyte> 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<ammo_count; i++){
GET_SHORT(s_val);
if (i < MAX_SHIP_SECONDARY_BANKS) {
ammo[i] = s_val;
}
}
if (ammo_count > 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; i<ammo_count; i++){
shipp->weapons.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<short>(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[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) {
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 ( (rnum >= 0) && (rnum < Num_reinforcements) ) {
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<ushort>(timestamp_since(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 (objp_ref == nullptr || 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<int>(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<int>(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<short>(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<short>(shooter_subsys_index));
ADD_SHORT(static_cast<short>(target_subsys_index));
val = static_cast<ubyte>(fire_info->fire_method);
ADD_DATA(val);
if (fire_info->bfi_flags & BFIF_IS_FIGHTER_BEAM) {
ADD_SHORT(static_cast<short>(fire_info->bank));
ADD_SHORT(static_cast<short>(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;
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!");
if ( (bank < 0) || (point < 0) ) {
nprintf(("Network", "Couldn't get firing point for fighter BEAM weapon!\n"));
return;
}
polymodel *pm = model_get( Ship_info[shipp->ship_info_index].model_num );
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)
{
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<short>(Weapons[Objects[weapon_objnum].instance].weapon_info_index) );
ADD_SHORT( static_cast<short>(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 );
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;
// 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 );
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
vm_vector_2_matrix_norm(&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,
};
// create the weapon object
weapon_objnum = weapon_create( &pos, &orient, wid, OBJ_INDEX(objp), -1, true, false, 0.0f, ssp, 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(make_unique<EffectHostVector>(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; idx<MAX_PLAYERS; idx++){
if(MULTI_CONNECTED(Net_players[idx]) && (Net_players[idx].player_id == player_id)){
HUD_printf("%s is the new captain of team %d", Net_players[idx].m_player->callsign, Net_players[idx].p_info.team + 1);
break;
}
}
} else {
// unflag all old players
for(idx=0; idx<MAX_PLAYERS; idx++){
Net_players[idx].flags &= ~NETINFO_FLAG_GAME_HOST;
}
// flag the new guy
found_player = 0;
for(idx=0; idx<MAX_PLAYERS; idx++){
if(MULTI_CONNECTED(Net_players[idx]) && (Net_players[idx].player_id == player_id)){
Net_players[idx].flags |= NETINFO_FLAG_GAME_HOST;
// spew to the HUD config
if(Net_players[idx].m_player != NULL){
HUD_printf("%s is the new game host", Net_players[idx].m_player->callsign);
}
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<int>(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<ushort>(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<int> send_turret_tracking_packet(ushort parent_sig, SCP_vector<int>& 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<int> 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<short>(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<bool, float> angle1;
std::pair<bool, float> 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();
}