Files
fs2open.github.com/code/network/multi_obj.cpp
T
Zacam 4952b70488 Trunk Sync - r10911
There are a lot of changes here that should probably be examined, namely the Debug Console (which had conflicts) and Yarn's "any resolution" mainhalls code (Which also had conflicts) but for the most part this should be fairly clean.

git-svn-id: svn://svn.icculus.org/fs2open/branches/antipodes@10912 387891d4-d844-0410-90c0-e4c51a9137d3
2014-09-11 19:22:13 +10:00

2060 lines
58 KiB
C++

/*
* Copyright (C) Volition, Inc. 1999. All rights reserved.
*
* All source code herein is the property of Volition, Inc. You may not sell
* or otherwise commercially exploit the source or things you created based on the
* source.
*
*/
#include "network/multi_obj.h"
#include "globalincs/globals.h"
#include "freespace2/freespace.h"
#include "io/timer.h"
#include "io/key.h"
#include "globalincs/linklist.h"
#include "network/multimsgs.h"
#include "network/multiutil.h"
#include "network/multi_options.h"
#include "network/multi_rate.h"
#include "network/multi.h"
#include "object/object.h"
#include "ship/ship.h"
#include "playerman/player.h"
#include "math/spline.h"
#include "physics/physics.h"
#include "ship/afterburner.h"
#include "cfile/cfile.h"
#include "debugconsole/console.h"
// ---------------------------------------------------------------------------------------------------
// OBJECT UPDATE DEFINES/VARS
//
// test stuff
float oo_arrive_time[MAX_SHIPS][5]; // the last 5 arrival times for each ship
int oo_arrive_time_count[MAX_SHIPS]; // size of the arrival queue
float oo_arrive_time_avg_diff[MAX_SHIPS]; // the average time between arrivals
float oo_arrive_time_next[MAX_SHIPS]; // how many seconds have gone by. should be equal to oo_arrive_time_avg_diff[] the next time we get an update
// interp stuff
int oo_interp_count[MAX_SHIPS];
vec3d oo_interp_points[MAX_SHIPS][2];
bez_spline oo_interp_splines[MAX_SHIPS][2];
void multi_oo_calc_interp_splines(int ship_index, vec3d *cur_pos, matrix *cur_orient, physics_info *cur_phys_info, vec3d *new_pos, matrix *new_orient, physics_info *new_phys_info);
// HACK!!!
bool Multi_oo_afterburn_hack = false;
// how much data we're willing to put into a given oo packet
#define OO_MAX_SIZE 480
// tolerance for bashing position
#define OO_POS_UPDATE_TOLERANCE 100.0f
// new improved - more compacted info type
#define OO_POS_NEW (1<<0) //
#define OO_ORIENT_NEW (1<<1) //
#define OO_HULL_NEW (1<<2) // Hull AND shields
#define OO_AFTERBURNER_NEW (1<<3) //
#define OO_SUBSYSTEMS_AND_AI_NEW (1<<4) //
#define OO_PRIMARY_BANK (1<<5) // if this is set, fighter has selected bank one
#define OO_PRIMARY_LINKED (1<<6) // if this is set, banks are linked
#define OO_TRIGGER_DOWN (1<<7) // if this is set, trigger is DOWN
#define OO_VIEW_CONE_DOT (0.1f)
#define OO_VIEW_DIFF_TOL (0.15f) // if the dotproducts differ this far between frames, he's coming into view
// no timestamp should ever have sat for longer than this.
#define OO_MAX_TIMESTAMP 2500
// distance class
#define OO_NEAR 0
#define OO_NEAR_DIST (200.0f)
#define OO_MIDRANGE 1
#define OO_MIDRANGE_DIST (600.0f)
#define OO_FAR 2
#define OO_FAR_DIST (1400.0f)
// how often we should send full hull/shield updates
#define OO_HULL_SHIELD_TIME 600
#define OO_SUBSYS_TIME 1000
// timestamp values for object update times based on client's update level.
int Multi_oo_target_update_times[MAX_OBJ_UPDATE_LEVELS] =
{
100, // 15x a second
100, // 15x a second
66, // 30x a second
66,
};
// for near ships
int Multi_oo_front_near_update_times[MAX_OBJ_UPDATE_LEVELS] =
{
150, // low update
100, // medium update
66, // high update
66,
};
// for medium ships
int Multi_oo_front_medium_update_times[MAX_OBJ_UPDATE_LEVELS] =
{
250, // low update
180, // medium update
120, // high update
66,
};
// for far ships
int Multi_oo_front_far_update_times[MAX_OBJ_UPDATE_LEVELS] =
{
750, // low update
350, // medium update
150, // high update
66,
};
// for near ships
int Multi_oo_rear_near_update_times[MAX_OBJ_UPDATE_LEVELS] =
{
300, // low update
200, // medium update
100, // high update
66,
};
// for medium ships
int Multi_oo_rear_medium_update_times[MAX_OBJ_UPDATE_LEVELS] =
{
800, // low update
600, // medium update
300, // high update
66,
};
// for far ships
int Multi_oo_rear_far_update_times[MAX_OBJ_UPDATE_LEVELS] =
{
2500, // low update
1500, // medium update
400, // high update
66,
};
// ship index list for possibly sorting ships based upon distance, etc
short OO_ship_index[MAX_SHIPS];
int OO_update_index = -1; // index into OO_update_records for displaying update record info
// ---------------------------------------------------------------------------------------------------
// OBJECT UPDATE FUNCTIONS
//
object *OO_player_obj;
int OO_sort = 1;
int multi_oo_sort_func(const void *ship1, const void *ship2)
{
object *obj1, *obj2;
short index1, index2;
float dist1, dist2;
float dot1, dot2;
vec3d v1, v2;
vec3d vn1, vn2;
// get the 2 indices
memcpy(&index1, ship1, sizeof(short));
memcpy(&index2, ship2, sizeof(short));
// if the indices are bogus, or the objnums are bogus, return ">"
if((index1 < 0) || (index2 < 0) || (Ships[index1].objnum < 0) || (Ships[index2].objnum < 0)){
return 1;
}
// get the 2 objects
obj1 = &Objects[Ships[index1].objnum];
obj2 = &Objects[Ships[index2].objnum];
// get the distance and dot product to the player obj for both
vm_vec_sub(&v1, &OO_player_obj->pos, &obj1->pos);
dist1 = vm_vec_copy_normalize(&vn1, &v1);
vm_vec_sub(&v2, &OO_player_obj->pos, &obj2->pos);
dist2 = vm_vec_copy_normalize(&vn2, &v2);
dot1 = vm_vec_dotprod(&OO_player_obj->orient.vec.fvec, &vn1);
dot2 = vm_vec_dotprod(&OO_player_obj->orient.vec.fvec, &vn2);
// objects in front take precedence
if((dot1 < 0.0f) && (dot2 >= 0.0f)){
return 1;
} else if((dot2 < 0.0f) && (dot1 >= 0.0f)){
return -1;
}
// otherwise go by distance
return (dist1 <= dist2) ? -1 : 1;
}
// build the list of ship indices to use when updating for this player
void multi_oo_build_ship_list(net_player *pl)
{
int ship_index;
int idx;
ship_obj *moveup;
object *player_obj;
// set all indices to be -1
for(idx = 0;idx<MAX_SHIPS; idx++){
OO_ship_index[idx] = -1;
}
// get the player object
if(pl->m_player->objnum < 0){
return;
}
player_obj = &Objects[pl->m_player->objnum];
// go through all other relevant objects
ship_index = 0;
for ( moveup = GET_FIRST(&Ship_obj_list); moveup != END_OF_LIST(&Ship_obj_list); moveup = GET_NEXT(moveup) ) {
// if it is an invalid ship object, skip it
if((moveup->objnum < 0) || (Objects[moveup->objnum].instance < 0) || (Objects[moveup->objnum].type != OBJ_SHIP)){
continue;
}
// if we're a standalone server, don't send any data regarding its pseudo-ship
if((Game_mode & GM_STANDALONE_SERVER) && ((&Objects[moveup->objnum] == Player_obj) || (Objects[moveup->objnum].net_signature == STANDALONE_SHIP_SIG)) ){
continue;
}
// must be a ship, a weapon, and _not_ an observer
if (Objects[moveup->objnum].flags & OF_SHOULD_BE_DEAD){
continue;
}
// don't send info for dying ships
if (Ships[Objects[moveup->objnum].instance].flags & SF_DYING){
continue;
}
// never update the knossos device
if ((Ships[Objects[moveup->objnum].instance].ship_info_index >= 0) && (Ships[Objects[moveup->objnum].instance].ship_info_index < Num_ship_classes) && (Ship_info[Ships[Objects[moveup->objnum].instance].ship_info_index].flags & SIF_KNOSSOS_DEVICE)){
continue;
}
// don't send him info for himself
if ( &Objects[moveup->objnum] == player_obj ){
continue;
}
// don't send info for his targeted ship here, since its always done first
if((pl->s_info.target_objnum != -1) && (moveup->objnum == pl->s_info.target_objnum)){
continue;
}
// add the ship
if(ship_index < MAX_SHIPS){
OO_ship_index[ship_index++] = (short)Objects[moveup->objnum].instance;
}
}
// maybe qsort the thing here
OO_player_obj = player_obj;
if(OO_sort){
qsort(OO_ship_index, ship_index, sizeof(short), multi_oo_sort_func);
}
}
// pack information for a client (myself), return bytes added
int multi_oo_pack_client_data(ubyte *data)
{
ubyte out_flags;
ushort tnet_signature;
char t_subsys, l_subsys;
int packet_size = 0;
// get our firing stuff
out_flags = Net_player->s_info.accum_buttons;
// zero these values for now
Net_player->s_info.accum_buttons = 0;
// add any necessary targeting flags
if ( Player_ai->current_target_is_locked ){
out_flags |= OOC_TARGET_LOCKED;
}
if ( Player_ai->ai_flags & AIF_SEEK_LOCK ){
out_flags |= OOC_TARGET_SEEK_LOCK;
}
if ( Player->locking_on_center ){
out_flags |= OOC_LOCKING_ON_CENTER;
}
if ( (Player_ship != NULL) && (Player_ship->flags & SF_TRIGGER_DOWN) ){
out_flags |= OOC_TRIGGER_DOWN;
}
if ( (Player_obj != NULL) && Player_obj->phys_info.flags & PF_AFTERBURNER_ON){
out_flags |= OOC_AFTERBURNER_ON;
}
// send my bank info
if(Player_ship != NULL){
if(Player_ship->weapons.current_primary_bank > 0){
out_flags |= OOC_PRIMARY_BANK;
}
// linked or not
if(Player_ship->flags & SF_PRIMARY_LINKED){
out_flags |= OOC_PRIMARY_LINKED;
}
}
// copy the final flags in
ADD_DATA( out_flags );
// client targeting information
t_subsys = -1;
l_subsys = -1;
// if nothing targeted
if(Player_ai->target_objnum == -1){
tnet_signature = 0;
}
// if something is targeted
else {
// target net signature
tnet_signature = Objects[Player_ai->target_objnum].net_signature;
// targeted subsys index
if(Player_ai->targeted_subsys != NULL){
t_subsys = (char)ship_get_index_from_subsys( Player_ai->targeted_subsys, Player_ai->target_objnum );
}
// locked targeted subsys index
if(Player->locking_subsys != NULL){
l_subsys = (char)ship_get_index_from_subsys( Player->locking_subsys, Player_ai->target_objnum, 1 );
}
}
// add them all
ADD_USHORT( tnet_signature );
ADD_DATA( t_subsys );
ADD_DATA( l_subsys );
return packet_size;
}
// pack the appropriate info into the data
#define PACK_PERCENT(v) { ubyte upercent; if(v < 0.0f){v = 0.0f;} upercent = (v * 255.0f) <= 255.0f ? (ubyte)(v * 255.0f) : (ubyte)255; memcpy(data + packet_size + header_bytes, &upercent, sizeof(ubyte)); packet_size++; }
#define PACK_BYTE(v) { memcpy( data + packet_size + header_bytes, &v, 1 ); packet_size += 1; }
#define PACK_USHORT(v) { short swap = INTEL_SHORT(v); memcpy( data + packet_size + header_bytes, &swap, sizeof(short) ); packet_size += sizeof(short); }
#define PACK_SHORT(v) { ushort swap = INTEL_SHORT(v); memcpy( data + packet_size + header_bytes, &swap, sizeof(ushort) ); packet_size += sizeof(ushort); }
#define PACK_INT(v) { int swap = INTEL_INT(v); memcpy( data + packet_size + header_bytes, &swap, sizeof(int) ); packet_size += sizeof(int); }
int multi_oo_pack_data(net_player *pl, object *objp, ubyte oo_flags, ubyte *data_out)
{
ubyte data[255];
ubyte data_size = 0;
char percent;
ship *shipp;
ship_info *sip;
ubyte ret;
float temp;
int header_bytes;
int packet_size = 0;
// make sure we have a valid ship
Assert(objp->type == OBJ_SHIP);
if((objp->instance >= 0) && (Ships[objp->instance].ship_info_index >= 0)){
shipp = &Ships[objp->instance];
sip = &Ship_info[shipp->ship_info_index];
} else {
return 0;
}
// invalid player
if(pl == NULL){
return 0;
}
// no flags -> do nothing
if(oo_flags == 0){
return 0;
}
// if i'm the client, make sure I only send certain things
if(!MULTIPLAYER_MASTER){
Assert(oo_flags & (OO_POS_NEW | OO_ORIENT_NEW));
Assert(!(oo_flags & (OO_HULL_NEW | OO_SUBSYSTEMS_AND_AI_NEW)));
}
// server
else {
// Assert(oo_flags & OO_POS_NEW);
}
// header sizes
if(MULTIPLAYER_MASTER){
header_bytes = 5;
} else {
header_bytes = 2;
}
// if we're a client (and therefore sending control info), pack client-specific info
if((Net_player != NULL) && !(Net_player->flags & NETINFO_FLAG_AM_MASTER)){
packet_size += multi_oo_pack_client_data(data + packet_size + header_bytes);
}
// position, velocity
if ( oo_flags & OO_POS_NEW ) {
ret = (ubyte)multi_pack_unpack_position( 1, data + packet_size + header_bytes, &objp->pos );
packet_size += ret;
// global records
multi_rate_add(NET_PLAYER_NUM(pl), "pos", ret);
ret = (ubyte)multi_pack_unpack_vel( 1, data + packet_size + header_bytes, &objp->orient, &objp->pos, &objp->phys_info );
packet_size += ret;
// global records
multi_rate_add(NET_PLAYER_NUM(pl), "pos", ret);
}
// orientation
if(oo_flags & OO_ORIENT_NEW){
ret = (ubyte)multi_pack_unpack_orient( 1, data + packet_size + header_bytes, &objp->orient );
// Assert(ret == OO_ORIENT_RET_SIZE);
packet_size += ret;
multi_rate_add(NET_PLAYER_NUM(pl), "ori", ret);
ret = (ubyte)multi_pack_unpack_rotvel( 1, data + packet_size + header_bytes, &objp->orient, &objp->pos, &objp->phys_info );
packet_size += ret;
// global records
multi_rate_add(NET_PLAYER_NUM(pl), "ori", ret);
}
// forward thrust
percent = (char)(objp->phys_info.forward_thrust * 100.0f);
Assert( percent <= 100 );
PACK_BYTE( percent );
// global records
multi_rate_add(NET_PLAYER_NUM(pl), "fth", 1);
// hull info
if ( oo_flags & OO_HULL_NEW ){
// add the hull value for this guy
temp = get_hull_pct(objp);
if ( (temp < 0.004f) && (temp > 0.0f) ) {
temp = 0.004f; // 0.004 is the lowest positive value we can have before we zero out when packing
}
PACK_PERCENT(temp);
multi_rate_add(NET_PLAYER_NUM(pl), "hul", 1);
float quad = get_max_shield_quad(objp);
for (int i = 0; i < objp->n_quadrants; i++) {
temp = (objp->shield_quadrant[i] / quad);
PACK_PERCENT(temp);
}
multi_rate_add(NET_PLAYER_NUM(pl), "shl", objp->n_quadrants);
}
// subsystem info
if( oo_flags & OO_SUBSYSTEMS_AND_AI_NEW ){
ubyte ns;
ship_subsys *subsysp;
// just in case we have some kind of invalid data (should've been taken care of earlier in this function)
if(shipp->ship_info_index < 0){
ns = 0;
PACK_BYTE( ns );
multi_rate_add(NET_PLAYER_NUM(pl), "sub", 1);
}
// add the # of subsystems, and their data
else {
ns = (ubyte)Ship_info[shipp->ship_info_index].n_subsystems;
PACK_BYTE( ns );
multi_rate_add(NET_PLAYER_NUM(pl), "sub", 1);
// now the subsystems.
for ( subsysp = GET_FIRST(&shipp->subsys_list); subsysp != END_OF_LIST(&shipp->subsys_list); subsysp = GET_NEXT(subsysp) ) {
temp = (float)subsysp->current_hits / (float)subsysp->max_hits;
PACK_PERCENT(temp);
multi_rate_add(NET_PLAYER_NUM(pl), "sub", 1);
}
}
// ai mode info
ubyte umode = (ubyte)(Ai_info[shipp->ai_index].mode);
short submode = (short)(Ai_info[shipp->ai_index].submode);
ushort target_signature;
target_signature = 0;
if ( Ai_info[shipp->ai_index].target_objnum != -1 ){
target_signature = Objects[Ai_info[shipp->ai_index].target_objnum].net_signature;
}
PACK_BYTE( umode );
PACK_SHORT( submode );
PACK_USHORT( target_signature );
multi_rate_add(NET_PLAYER_NUM(pl), "aim", 5);
// primary weapon energy
temp = shipp->weapon_energy / sip->max_weapon_reserve;
PACK_PERCENT(temp);
}
// afterburner info
oo_flags &= ~PF_AFTERBURNER_ON;
if(objp->phys_info.flags & PF_AFTERBURNER_ON){
oo_flags |= OO_AFTERBURNER_NEW;
}
// if this ship is a support ship, send some extra info
ubyte support_extra = 0;
if(MULTIPLAYER_MASTER && (sip->flags & SIF_SUPPORT) && (shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO)){
ushort dock_sig;
// flag
support_extra = 1;
PACK_BYTE( support_extra );
PACK_INT( Ai_info[shipp->ai_index].ai_flags );
PACK_INT( Ai_info[shipp->ai_index].mode );
PACK_INT( Ai_info[shipp->ai_index].submode );
if((Ai_info[shipp->ai_index].support_ship_objnum < 0) || (Ai_info[shipp->ai_index].support_ship_objnum >= MAX_OBJECTS)){
dock_sig = 0;
} else {
dock_sig = Objects[Ai_info[shipp->ai_index].support_ship_objnum].net_signature;
}
PACK_USHORT( dock_sig );
} else {
support_extra = 0;
PACK_BYTE( support_extra );
}
// make sure we have a valid chunk of data
// Clients: must be able to accomodate the data_size and shipp->np_updates[NET_PLAYER_NUM(pl)].seq before the data itself
// Server: TODO
Assert(packet_size < 255-1);
if(packet_size >= 255-1){
return 0;
}
data_size = (ubyte)packet_size;
// add the object's net signature, type and oo_flags
packet_size = 0;
// don't add for clients
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_rate_add(NET_PLAYER_NUM(pl), "sig", 2);
ADD_USHORT( objp->net_signature );
multi_rate_add(NET_PLAYER_NUM(pl), "flg", 1);
ADD_DATA( oo_flags );
}
multi_rate_add(NET_PLAYER_NUM(pl), "siz", 1);
ADD_DATA( data_size );
multi_rate_add(NET_PLAYER_NUM(pl), "seq", 1);
ADD_DATA( shipp->np_updates[NET_PLAYER_NUM(pl)].seq );
packet_size += data_size;
// copy to the outgoing data
memcpy(data_out, data, packet_size);
return packet_size;
}
// unpack information for a client , return bytes processed
int multi_oo_unpack_client_data(net_player *pl, ubyte *data)
{
ubyte in_flags;
ship *shipp = NULL;
object *objp = NULL;
int offset = 0;
if (pl == NULL)
Error(LOCATION, "Invalid net_player pointer passed to multi_oo_unpack_client\n");
memcpy(&in_flags, data, sizeof(ubyte));
offset++;
// get the player ship and object
if((pl->m_player->objnum >= 0) && (Objects[pl->m_player->objnum].type == OBJ_SHIP) && (Objects[pl->m_player->objnum].instance >= 0)){
objp = &Objects[pl->m_player->objnum];
shipp = &Ships[objp->instance];
}
// if we have a valid netplayer pointer
if((pl != NULL) && !(pl->flags & NETINFO_FLAG_RESPAWNING) && !(pl->flags & NETINFO_FLAG_LIMBO)){
// primary fired
pl->m_player->ci.fire_primary_count = 0;
// secondary fired
pl->m_player->ci.fire_secondary_count = 0;
if ( in_flags & OOC_FIRE_SECONDARY ){
pl->m_player->ci.fire_secondary_count = 1;
}
// countermeasure fired
pl->m_player->ci.fire_countermeasure_count = 0;
// set up aspect locking information
pl->m_player->locking_on_center = 0;
if ( in_flags & OOC_LOCKING_ON_CENTER ){
pl->m_player->locking_on_center = 1;
}
// trigger down, bank info
if(shipp != NULL){
if(in_flags & OOC_TRIGGER_DOWN){
shipp->flags |= SF_TRIGGER_DOWN;
} else {
shipp->flags &= ~SF_TRIGGER_DOWN;
}
if(in_flags & OOC_PRIMARY_BANK){
shipp->weapons.current_primary_bank = 1;
} else {
shipp->weapons.current_primary_bank = 0;
}
// linked or not
shipp->flags &= ~SF_PRIMARY_LINKED;
if(in_flags & OOC_PRIMARY_LINKED){
shipp->flags |= SF_PRIMARY_LINKED;
}
}
// other locking information
if((shipp != NULL) && (shipp->ai_index != -1)){
Ai_info[shipp->ai_index].current_target_is_locked = ( in_flags & OOC_TARGET_LOCKED) ? 1 : 0;
if ( in_flags & OOC_TARGET_SEEK_LOCK ) {
Ai_info[shipp->ai_index].ai_flags |= AIF_SEEK_LOCK;
} else {
Ai_info[shipp->ai_index].ai_flags &= ~AIF_SEEK_LOCK;
}
}
// afterburner status
if ( (objp != NULL) && (in_flags & OOC_AFTERBURNER_ON) ) {
Multi_oo_afterburn_hack = true;
}
}
// client targeting information
ushort tnet_sig;
char t_subsys,l_subsys;
object *tobj;
// get the data
GET_USHORT(tnet_sig);
GET_DATA(t_subsys);
GET_DATA(l_subsys);
// try and find the targeted object
tobj = NULL;
if(tnet_sig != 0){
tobj = multi_get_network_object( tnet_sig );
}
// maybe fill in targeted object values
if((tobj != NULL) && (pl != NULL) && (pl->m_player->objnum != -1)){
// assign the target object
if(Objects[pl->m_player->objnum].type == OBJ_SHIP){
Ai_info[Ships[Objects[pl->m_player->objnum].instance].ai_index].target_objnum = OBJ_INDEX(tobj);
}
pl->s_info.target_objnum = OBJ_INDEX(tobj);
// assign subsystems if possible
if(Objects[pl->m_player->objnum].type == OBJ_SHIP){
Ai_info[Ships[Objects[pl->m_player->objnum].instance].ai_index].targeted_subsys = NULL;
if((t_subsys != -1) && (tobj->type == OBJ_SHIP)){
Ai_info[Ships[Objects[pl->m_player->objnum].instance].ai_index].targeted_subsys = ship_get_indexed_subsys( &Ships[tobj->instance], t_subsys);
}
}
pl->m_player->locking_subsys = NULL;
if(Objects[pl->m_player->objnum].type == OBJ_SHIP){
if((l_subsys != -1) && (tobj->type == OBJ_SHIP)){
pl->m_player->locking_subsys = ship_get_indexed_subsys( &Ships[tobj->instance], l_subsys);
}
}
}
return offset;
}
// unpack the object data, return bytes processed
#define UNPACK_PERCENT(v) { ubyte temp_byte; memcpy(&temp_byte, data + offset, sizeof(ubyte)); v = (float)temp_byte / 255.0f; offset++;}
int multi_oo_unpack_data(net_player *pl, ubyte *data)
{
int offset = 0;
object *pobjp;
ushort net_sig = 0;
ubyte data_size, oo_flags;
ubyte seq_num;
char percent;
float fpct;
ship *shipp;
ship_info *sip;
// add the object's net signature, type and oo_flags
if(!(Net_player->flags & NETINFO_FLAG_AM_MASTER)){
GET_USHORT( net_sig );
GET_DATA( oo_flags );
}
// clients always pos and orient stuff only
else {
oo_flags = (OO_POS_NEW | OO_ORIENT_NEW);
}
GET_DATA( data_size );
GET_DATA( seq_num );
// try and find the object
if(!(Net_player->flags & NETINFO_FLAG_AM_MASTER)){
pobjp = multi_get_network_object(net_sig);
} else {
if((pl != NULL) && (pl->m_player->objnum != -1)){
pobjp = &Objects[pl->m_player->objnum];
} else {
pobjp = NULL;
}
}
// if we can't find the object, set pointer to bogus object to continue reading the data
// ignore out of sequence packets here as well
if ( (pobjp == NULL) || (pobjp->type != OBJ_SHIP) || (pobjp->instance < 0) || (pobjp->instance >= MAX_SHIPS) || (Ships[pobjp->instance].ship_info_index < 0) || (Ships[pobjp->instance].ship_info_index >= Num_ship_classes)){
offset += data_size;
return offset;
}
// ship pointer
shipp = &Ships[pobjp->instance];
sip = &Ship_info[shipp->ship_info_index];
// ---------------------------------------------------------------------------------------------------------------
// CRITICAL OBJECT UPDATE SHIZ
// ---------------------------------------------------------------------------------------------------------------
// if the packet is out of order
if(seq_num < shipp->np_updates[NET_PLAYER_NUM(pl)].seq){
// non-wraparound case
if((shipp->np_updates[NET_PLAYER_NUM(pl)].seq - seq_num) <= 100){
offset += data_size;
return offset;
}
}
// make sure the ab hack is reset before we read in new info
Multi_oo_afterburn_hack = false;
// if this is from a player, read his button info
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
offset += multi_oo_unpack_client_data(pl, data + offset);
}
// new info
vec3d new_pos = pobjp->pos;
physics_info new_phys_info = pobjp->phys_info;
matrix new_orient = pobjp->orient;
// position
if ( oo_flags & OO_POS_NEW ) {
// AVERAGE TIME BETWEEN PACKETS FOR THIS SHIP
// store this latest time stamp
if(oo_arrive_time_count[shipp - Ships] == 5){
memmove(&oo_arrive_time[shipp - Ships][0], &oo_arrive_time[shipp - Ships][1], sizeof(float) * 4);
oo_arrive_time[shipp - Ships][4] = f2fl(Missiontime);
} else {
oo_arrive_time[shipp - Ships][oo_arrive_time_count[shipp - Ships]++] = f2fl(Missiontime);
}
// if we've got 5 elements calculate the average
if(oo_arrive_time_count[shipp - Ships] == 5){
int idx;
oo_arrive_time_avg_diff[shipp - Ships] = 0.0f;
for(idx=0; idx<4; idx++){
oo_arrive_time_avg_diff[shipp - Ships] += oo_arrive_time[shipp - Ships][idx + 1] - oo_arrive_time[shipp - Ships][idx];
}
oo_arrive_time_avg_diff[shipp - Ships] /= 5.0f;
}
// next expected arrival time
oo_arrive_time_next[shipp - Ships] = 0.0f;
// int r1 = multi_pack_unpack_position( 0, data + offset, &pobjp->pos );
int r1 = multi_pack_unpack_position( 0, data + offset, &new_pos );
offset += r1;
// int r3 = multi_pack_unpack_vel( 0, data + offset, &pobjp->orient, &pobjp->pos, &pobjp->phys_info );
int r3 = multi_pack_unpack_vel( 0, data + offset, &pobjp->orient, &new_pos, &new_phys_info );
offset += r3;
// bash desired vel to be velocity
// pobjp->phys_info.desired_vel = pobjp->phys_info.vel;
}
// orientation
if ( oo_flags & OO_ORIENT_NEW ) {
// int r2 = multi_pack_unpack_orient( 0, data + offset, &pobjp->orient );
int r2 = multi_pack_unpack_orient( 0, data + offset, &new_orient );
offset += r2;
// int r5 = multi_pack_unpack_rotvel( 0, data + offset, &pobjp->orient, &pobjp->pos, &pobjp->phys_info );
int r5 = multi_pack_unpack_rotvel( 0, data + offset, &new_orient, &new_pos, &new_phys_info );
offset += r5;
// bash desired rotvel to be 0
// pobjp->phys_info.desired_rotvel = vmd_zero_vector;
}
// forward thrust
percent = (char)(pobjp->phys_info.forward_thrust * 100.0f);
Assert( percent <= 100 );
GET_DATA(percent);
// now stuff all this new info
if(oo_flags & OO_POS_NEW){
// if we're past the position update tolerance, bash.
// this should cause our 2 interpolation splines to be exactly the same. so we'll see a jump,
// but it should be nice and smooth immediately afterwards
if(vm_vec_dist(&new_pos, &pobjp->pos) > OO_POS_UPDATE_TOLERANCE){
pobjp->pos = new_pos;
}
// recalc any interpolation info
if(oo_interp_count[shipp - Ships] < 2){
oo_interp_points[shipp - Ships][oo_interp_count[shipp - Ships]++] = new_pos;
} else {
oo_interp_points[shipp - Ships][0] = oo_interp_points[shipp - Ships][1];
oo_interp_points[shipp - Ships][1] = new_pos;
multi_oo_calc_interp_splines(shipp - Ships, &pobjp->pos, &pobjp->orient, &pobjp->phys_info, &new_pos, &new_orient, &new_phys_info);
}
pobjp->phys_info.vel = new_phys_info.vel;
pobjp->phys_info.desired_vel = new_phys_info.vel;
}
// we'll just sim rotation straight. it works fine.
if(oo_flags & OO_ORIENT_NEW){
pobjp->orient = new_orient;
pobjp->phys_info.rotvel = new_phys_info.rotvel;
// pobjp->phys_info.desired_rotvel = vmd_zero_vector;
pobjp->phys_info.desired_rotvel = new_phys_info.rotvel;
}
// ---------------------------------------------------------------------------------------------------------------
// ANYTHING BELOW HERE WORKS FINE - nothing here which causes jumpiness or bandwidth problems :) WHEEEEE!
// ---------------------------------------------------------------------------------------------------------------
// hull info
if ( oo_flags & OO_HULL_NEW ){
UNPACK_PERCENT(fpct);
pobjp->hull_strength = fpct * Ships[pobjp->instance].ship_max_hull_strength;
float quad = get_max_shield_quad(pobjp);
for (int i = 0; i < pobjp->n_quadrants; i++) {
UNPACK_PERCENT(fpct);
pobjp->shield_quadrant[i] = fpct * quad;
}
}
if ( oo_flags & OO_SUBSYSTEMS_AND_AI_NEW ) {
ubyte n_subsystems, subsys_count;
float subsystem_percent[MAX_MODEL_SUBSYSTEMS];
ship_subsys *subsysp;
float val;
int i;
// get the data for the subsystems
GET_DATA( n_subsystems );
for ( i = 0; i < n_subsystems; i++ ){
UNPACK_PERCENT( subsystem_percent[i] );
}
// fill in the subsystem data
subsys_count = 0;
for ( subsysp = GET_FIRST(&shipp->subsys_list); subsysp != END_OF_LIST(&shipp->subsys_list); subsysp = GET_NEXT(subsysp) ) {
int subsys_type;
val = subsystem_percent[subsys_count] * subsysp->max_hits;
subsysp->current_hits = 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 & SSF_NO_AGGREGATE)) {
shipp->subsys_info[subsys_type].aggregate_current_hits += val;
}
subsys_count++;
// if we've reached max subsystems for some reason, bail out
if(subsys_count >= n_subsystems){
break;
}
}
// recalculate all ship subsystems
ship_recalc_subsys_strength( shipp );
// ai mode info
ubyte umode;
short submode;
ushort target_signature;
object *target_objp;
GET_DATA( umode );
GET_SHORT( submode );
GET_USHORT( target_signature );
if(shipp->ai_index >= 0){
Ai_info[shipp->ai_index].mode = umode;
Ai_info[shipp->ai_index].submode = submode;
// set this guys target objnum
target_objp = multi_get_network_object( target_signature );
if ( target_objp == NULL ){
Ai_info[shipp->ai_index].target_objnum = -1;
} else {
Ai_info[shipp->ai_index].target_objnum = OBJ_INDEX(target_objp);
}
}
// primary weapon energy
float weapon_energy_pct;
UNPACK_PERCENT(weapon_energy_pct);
shipp->weapon_energy = sip->max_weapon_reserve * weapon_energy_pct;
}
// support ship extra info
ubyte support_extra;
GET_DATA(support_extra);
if(support_extra){
ushort dock_sig;
int ai_flags, ai_mode, ai_submode;
// flag
GET_INT(ai_flags);
GET_INT(ai_mode);
GET_INT(ai_submode);
GET_USHORT(dock_sig);
// valid ship?
if((shipp != NULL) && (shipp->ai_index >= 0) && (shipp->ai_index < MAX_AI_INFO)){
Ai_info[shipp->ai_index].ai_flags = ai_flags;
Ai_info[shipp->ai_index].mode = ai_mode;
Ai_info[shipp->ai_index].submode = ai_submode;
object *objp = multi_get_network_object( dock_sig );
if(objp != NULL){
Ai_info[shipp->ai_index].support_ship_objnum = OBJ_INDEX(objp);
}
}
}
// afterburner info
if ( (oo_flags & OO_AFTERBURNER_NEW) || Multi_oo_afterburn_hack ) {
// maybe turn them on
if(!(pobjp->phys_info.flags & PF_AFTERBURNER_ON)){
afterburners_start(pobjp);
}
// make sure the ab hack is reset before we read in new info
Multi_oo_afterburn_hack = false;
} else {
// maybe turn them off
if(pobjp->phys_info.flags & PF_AFTERBURNER_ON){
afterburners_stop(pobjp);
}
}
// primary info (only clients care about this)
if( !MULTIPLAYER_MASTER && (shipp != NULL) ){
// what bank
if(oo_flags & OO_PRIMARY_BANK){
shipp->weapons.current_primary_bank = 1;
} else {
shipp->weapons.current_primary_bank = 0;
}
// linked or not
shipp->flags &= ~SF_PRIMARY_LINKED;
if(oo_flags & OO_PRIMARY_LINKED){
shipp->flags |= SF_PRIMARY_LINKED;
}
// trigger down or not - server doesn't care about this. he'll get it from clients anyway
shipp->flags &= ~SF_TRIGGER_DOWN;
if(oo_flags & OO_TRIGGER_DOWN){
shipp->flags |= SF_TRIGGER_DOWN;
}
}
// if we're the multiplayer server, set eye position and orient
if(MULTIPLAYER_MASTER && (pl != NULL) && (pobjp != NULL)){
pl->s_info.eye_pos = pobjp->pos;
pl->s_info.eye_orient = pobjp->orient;
}
// update the sequence #
shipp->np_updates[NET_PLAYER_NUM(pl)].seq = seq_num;
// flag the object as just updated
// pobjp->flags |= OF_JUST_UPDATED;
return offset;
}
// reset the timestamp appropriately for the passed in object
void multi_oo_reset_timestamp(net_player *pl, object *objp, int range, int in_cone)
{
int stamp = 0;
// if this is the guy's target,
if((pl->s_info.target_objnum != -1) && (pl->s_info.target_objnum == OBJ_INDEX(objp))){
stamp = Multi_oo_target_update_times[pl->p_info.options.obj_update_level];
} else {
// reset the timestamp appropriately
if(in_cone){
// base it upon range
switch(range){
case OO_NEAR:
stamp = Multi_oo_front_near_update_times[pl->p_info.options.obj_update_level];
break;
case OO_MIDRANGE:
stamp = Multi_oo_front_medium_update_times[pl->p_info.options.obj_update_level];
break;
case OO_FAR:
stamp = Multi_oo_front_far_update_times[pl->p_info.options.obj_update_level];
break;
}
} else {
// base it upon range
switch(range){
case OO_NEAR:
stamp = Multi_oo_rear_near_update_times[pl->p_info.options.obj_update_level];
break;
case OO_MIDRANGE:
stamp = Multi_oo_rear_medium_update_times[pl->p_info.options.obj_update_level];
break;
case OO_FAR:
stamp = Multi_oo_rear_far_update_times[pl->p_info.options.obj_update_level];
break;
}
}
}
// reset the timestamp for this object
if(objp->type == OBJ_SHIP){
Ships[objp->instance].np_updates[NET_PLAYER_NUM(pl)].update_stamp = timestamp(stamp);
}
}
// reset the timestamp appropriately for the passed in object
void multi_oo_reset_status_timestamp(object *objp, int player_index)
{
Ships[objp->instance].np_updates[player_index].status_update_stamp = timestamp(OO_HULL_SHIELD_TIME);
}
// reset the timestamp appropriately for the passed in object
void multi_oo_reset_subsys_timestamp(object *objp, int player_index)
{
Ships[objp->instance].np_updates[player_index].subsys_update_stamp = timestamp(OO_SUBSYS_TIME);
}
// determine what needs to get sent for this player regarding the passed object, and when
int multi_oo_maybe_update(net_player *pl, object *obj, ubyte *data)
{
ubyte oo_flags;
int stamp;
int player_index;
vec3d player_eye;
vec3d obj_dot;
float eye_dot, dist;
int in_cone;
int range;
ship *shipp;
ship_info *sip;
ushort cur_pos_chksum = 0;
ushort cur_orient_chksum = 0;
// if the timestamp has elapsed for this guy, send stuff
player_index = NET_PLAYER_INDEX(pl);
if(!(player_index >= 0) || !(player_index < MAX_PLAYERS)){
return 0;
}
// determine what the timestamp is for this object
if(obj->type == OBJ_SHIP){
stamp = Ships[obj->instance].np_updates[NET_PLAYER_NUM(pl)].update_stamp;
} else {
return 0;
}
// stamp hasn't popped yet
if((stamp != -1) && !timestamp_elapsed_safe(stamp, OO_MAX_TIMESTAMP)){
return 0;
}
// if we're supposed to update this guy
// get the ship pointer
shipp = &Ships[obj->instance];
// get ship info pointer
sip = NULL;
if(shipp->ship_info_index >= 0){
sip = &Ship_info[shipp->ship_info_index];
}
// check dot products
player_eye = pl->s_info.eye_orient.vec.fvec;
vm_vec_sub(&obj_dot, &obj->pos, &pl->s_info.eye_pos);
in_cone = 0;
if (!(IS_VEC_NULL(&obj_dot))) {
vm_vec_normalize(&obj_dot);
eye_dot = vm_vec_dot(&obj_dot, &player_eye);
in_cone = (eye_dot >= OO_VIEW_CONE_DOT) ? 1 : 0;
}
// determine distance (near, medium, far)
vm_vec_sub(&obj_dot, &obj->pos, &pl->s_info.eye_pos);
dist = vm_vec_mag(&obj_dot);
if(dist < OO_NEAR_DIST){
range = OO_NEAR;
} else if(dist < OO_MIDRANGE_DIST){
range = OO_MIDRANGE;
} else {
range = OO_FAR;
}
// reset the timestamp for the next update for this guy
multi_oo_reset_timestamp(pl, obj, range, in_cone);
// base oo_flags
oo_flags = OO_POS_NEW | OO_ORIENT_NEW;
// if its a small ship, add weapon link info
if((sip != NULL) && (sip->flags & (SIF_FIGHTER | SIF_BOMBER))){
// primary bank 0 or 1
if(shipp->weapons.current_primary_bank > 0){
oo_flags |= OO_PRIMARY_BANK;
}
// linked or not
if(shipp->flags & SF_PRIMARY_LINKED){
oo_flags |= OO_PRIMARY_LINKED;
}
// trigger down or not
if(shipp->flags & SF_TRIGGER_DOWN){
oo_flags |= OO_TRIGGER_DOWN;
}
}
// if the object's hull/shield timestamp has expired
if((Ships[obj->instance].np_updates[player_index].status_update_stamp == -1) || timestamp_elapsed_safe(Ships[obj->instance].np_updates[player_index].status_update_stamp, OO_MAX_TIMESTAMP)){
oo_flags |= (OO_HULL_NEW);
// reset the timestamp
multi_oo_reset_status_timestamp(obj, player_index);
}
// if the object's hull/shield timestamp has expired
if((Ships[obj->instance].np_updates[player_index].subsys_update_stamp == -1) || timestamp_elapsed_safe(Ships[obj->instance].np_updates[player_index].subsys_update_stamp, OO_MAX_TIMESTAMP)){
oo_flags |= OO_SUBSYSTEMS_AND_AI_NEW;
// reset the timestamp
multi_oo_reset_subsys_timestamp(obj, player_index);
}
// add info for a targeted object
if((pl->s_info.target_objnum != -1) && (OBJ_INDEX(obj) == pl->s_info.target_objnum)){
oo_flags |= (OO_POS_NEW | OO_ORIENT_NEW | OO_HULL_NEW);
}
// all other cases
else {
// add info which is contingent upon being "in front"
if(in_cone){
oo_flags |= OO_ORIENT_NEW;
}
}
// get current position and orient checksums
cur_pos_chksum = cf_add_chksum_short(cur_pos_chksum, (ubyte*)(&obj->pos), sizeof(vec3d));
cur_orient_chksum = cf_add_chksum_short(cur_orient_chksum, (ubyte*)(&obj->orient), sizeof(matrix));
// if position or orientation haven't changed
if((shipp->np_updates[player_index].pos_chksum != 0) && (shipp->np_updates[player_index].pos_chksum == cur_pos_chksum)){
// if we otherwise would have been sending it, keep track of it (debug only)
#ifndef NDEBUG
if(oo_flags & OO_POS_NEW){
multi_rate_add(player_index, "skp_p", OO_POS_RET_SIZE + OO_VEL_RET_SIZE);
}
#endif
oo_flags &= ~(OO_POS_NEW);
}
if((shipp->np_updates[player_index].orient_chksum != 0) && (shipp->np_updates[player_index].orient_chksum == cur_orient_chksum)){
// if we otherwise would have been sending it, keep track of it (debug only)
#ifndef NDEBUG
if(oo_flags & OO_ORIENT_NEW){
multi_rate_add(player_index, "skp_o", OO_ORIENT_RET_SIZE + OO_ROTVEL_RET_SIZE);
}
#endif
oo_flags &= ~(OO_ORIENT_NEW);
}
shipp->np_updates[player_index].pos_chksum = cur_pos_chksum;
shipp->np_updates[player_index].orient_chksum = cur_orient_chksum;
// pack stuff only if we have to
int packed = multi_oo_pack_data(pl, obj, oo_flags ,data);
// increment sequence #
Ships[obj->instance].np_updates[NET_PLAYER_NUM(pl)].seq++;
// bytes packed
return packed;
}
// process all other objects for this player
void multi_oo_process_all(net_player *pl)
{
ubyte data[MAX_PACKET_SIZE];
ubyte data_add[MAX_PACKET_SIZE];
ubyte stop;
int add_size;
int packet_size = 0;
int idx;
object *moveup;
// if the player has an invalid objnum..
if(pl->m_player->objnum < 0){
return;
}
object *targ_obj;
// build the list of ships to check against
multi_oo_build_ship_list(pl);
// do nothing if he has no object targeted, or if he has a weapon targeted
if((pl->s_info.target_objnum != -1) && (Objects[pl->s_info.target_objnum].type == OBJ_SHIP)){
// build the header
BUILD_HEADER(OBJECT_UPDATE);
// get a pointer to the object
targ_obj = &Objects[pl->s_info.target_objnum];
// run through the maybe_update function
add_size = multi_oo_maybe_update(pl, targ_obj, data_add);
// copy in any relevant data
if(add_size){
stop = 0xff;
multi_rate_add(NET_PLAYER_NUM(pl), "stp", 1);
ADD_DATA(stop);
memcpy(data + packet_size, data_add, add_size);
packet_size += add_size;
}
} else {
// just build the header for the rest of the function
BUILD_HEADER(OBJECT_UPDATE);
}
idx = 0;
// rely on logical-AND shortcut evaluation to prevent array out-of-bounds read of OO_ship_index[idx]
while((idx < MAX_SHIPS) && (OO_ship_index[idx] >= 0)){
// if this guy is over his datarate limit, do nothing
if(multi_oo_rate_exceeded(pl)){
nprintf(("Network","Capping client\n"));
idx++;
continue;
}
// get the object
moveup = &Objects[Ships[OO_ship_index[idx]].objnum];
// maybe send some info
add_size = multi_oo_maybe_update(pl, moveup, data_add);
// if this data is too much for the packet, send off what we currently have and start over
if(packet_size + add_size > OO_MAX_SIZE){
stop = 0x00;
multi_rate_add(NET_PLAYER_NUM(pl), "stp", 1);
ADD_DATA(stop);
multi_io_send(pl, data, packet_size);
pl->s_info.rate_bytes += packet_size + UDP_HEADER_SIZE;
packet_size = 0;
BUILD_HEADER(OBJECT_UPDATE);
}
if(add_size){
stop = 0xff;
multi_rate_add(NET_PLAYER_NUM(pl), "stp", 1);
ADD_DATA(stop);
// copy in the data
memcpy(data + packet_size,data_add,add_size);
packet_size += add_size;
}
// next ship
idx++;
}
// if we have anything more than 3 byte in the packet, send the last one off
if(packet_size > 3){
stop = 0x00;
multi_rate_add(NET_PLAYER_NUM(pl), "stp", 1);
ADD_DATA(stop);
multi_io_send(pl, data, packet_size);
pl->s_info.rate_bytes += packet_size + UDP_HEADER_SIZE;
}
}
// process all object update details for this frame
void multi_oo_process()
{
int idx;
// process each player
for(idx=0; idx<MAX_PLAYERS; idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_STANDALONE(Net_players[idx]) && (Net_player != &Net_players[idx]) /*&& !MULTI_OBSERVER(Net_players[idx])*/ ){
// now process the rest of the objects
multi_oo_process_all(&Net_players[idx]);
// do firing stuff for this player
if((Net_players[idx].m_player != NULL) && (Net_players[idx].m_player->objnum >= 0) && !(Net_players[idx].flags & NETINFO_FLAG_LIMBO) && !(Net_players[idx].flags & NETINFO_FLAG_RESPAWNING)){
if((Objects[Net_players[idx].m_player->objnum].flags & OF_PLAYER_SHIP) && !(Objects[Net_players[idx].m_player->objnum].flags & OF_SHOULD_BE_DEAD)){
obj_player_fire_stuff( &Objects[Net_players[idx].m_player->objnum], Net_players[idx].m_player->ci );
}
}
}
}
}
// process incoming object update data
void multi_oo_process_update(ubyte *data, header *hinfo)
{
ubyte stop;
int player_index;
int offset = HEADER_LENGTH;
net_player *pl = NULL;
// determine what player this came from
player_index = find_player_id(hinfo->id);
if(player_index != -1){
pl = &Net_players[player_index];
}
// otherwise its a "regular" object update packet on a client from the server. use "myself" as the reference player
else {
pl = Net_player;
}
GET_DATA(stop);
while(stop == 0xff){
// process the data
offset += multi_oo_unpack_data(pl, data + offset);
GET_DATA(stop);
}
PACKET_SET_SIZE();
}
// initialize all object update timestamps (call whenever entering gameplay state)
void multi_oo_gameplay_init()
{
int cur, s_idx, idx;
//ship_obj *so;
ship *shipp;
/*
int num_ships = ship_get_num_ships();
if(num_ships <= 0){
return;
}
split = 3000 / num_ships;
*/
//split = 1;
//split = 150;
// server should setup initial update timestamps
// stagger initial updates over 3 seconds or so
cur = 0;
//for ( so = GET_FIRST(&Ship_obj_list); so != END_OF_LIST(&Ship_obj_list); so = GET_NEXT(so) ) {
//if(Objects[so->objnum].type == OBJ_SHIP){
for(s_idx=0; s_idx<MAX_SHIPS; s_idx++){
shipp = &Ships[s_idx];
// update the timestamps
for(idx=0;idx<MAX_PLAYERS;idx++){
shipp->np_updates[idx].update_stamp = timestamp(cur);
shipp->np_updates[idx].status_update_stamp = timestamp(cur);
shipp->np_updates[idx].subsys_update_stamp = timestamp(cur);
shipp->np_updates[idx].seq = 0;
shipp->np_updates[idx].pos_chksum = 0;
shipp->np_updates[idx].orient_chksum = 0;
}
oo_arrive_time_count[shipp - Ships] = 0;
oo_interp_count[shipp - Ships] = 0;
// increment the time
// cur += split;
}
//}
// reset datarate stamp now
extern int OO_gran;
for(idx=0; idx<MAX_PLAYERS; idx++){
Net_players[idx].s_info.rate_stamp = timestamp( (int)(1000.0f / (float)OO_gran) );
}
}
// send control info for a client (which is basically a "reverse" object update)
void multi_oo_send_control_info()
{
ubyte data[MAX_PACKET_SIZE], stop;
ubyte data_add[MAX_PACKET_SIZE];
ubyte oo_flags;
int add_size;
int packet_size = 0;
// if I'm dying or my object type is not a ship, bail here
if((Player_obj != NULL) && (Player_ship->flags & SF_DYING)){
return;
}
// build the header
BUILD_HEADER(OBJECT_UPDATE);
// pos and orient always
oo_flags = (OO_POS_NEW | OO_ORIENT_NEW);
// pack the appropriate info into the data
add_size = multi_oo_pack_data(Net_player, Player_obj, oo_flags, data_add);
// copy in any relevant data
if(add_size){
stop = 0xff;
multi_rate_add(NET_PLAYER_NUM(Net_player), "stp", 1);
ADD_DATA(stop);
memcpy(data + packet_size, data_add, add_size);
packet_size += add_size;
}
// add the final stop byte
stop = 0x0;
multi_rate_add(NET_PLAYER_NUM(Net_player), "stp", 1);
ADD_DATA(stop);
// increment sequence #
Player_ship->np_updates[MY_NET_PLAYER_NUM].seq++;
// send to the server
if(Netgame.server != NULL){
multi_io_send(Net_player, data, packet_size);
}
}
// Sends a packet from the server to the client, syncing the player's position/orientation to the
// Server's. Allows for use of certain SEXPs in multiplayer.
void multi_oo_send_changed_object(object *changedobj)
{
ubyte data[MAX_PACKET_SIZE], stop;
ubyte data_add[MAX_PACKET_SIZE];
ubyte oo_flags;
int add_size;
int packet_size = 0;
int idx = 0;
#ifndef NDEBUG
nprintf(("Network","Attempting to affect player object.\n"));
#endif
for (; idx < MAX_PLAYERS; idx++)
{
if( changedobj == &(Objects[Net_players[idx].m_player->objnum]) ) {
break;
}
}
#ifndef NDEBUG
nprintf(("Network","Index for changed object found: [%d].\n",idx));
#endif
if( idx >= MAX_PLAYERS ) {
return;
}
// build the header
BUILD_HEADER(OBJECT_UPDATE);
// pos and orient always
oo_flags = (OO_POS_NEW | OO_ORIENT_NEW);
// pack the appropriate info into the data
add_size = multi_oo_pack_data(&Net_players[idx], changedobj, oo_flags, data_add);
// copy in any relevant data
if(add_size){
stop = 0xff;
multi_rate_add(idx, "stp", 1);
ADD_DATA(stop);
memcpy(data + packet_size, data_add, add_size);
packet_size += add_size;
}
// add the final stop byte
stop = 0x0;
multi_rate_add(idx, "stp", 1);
ADD_DATA(stop);
// increment sequence #
// Player_ship->np_updates[idx].seq++;
multi_io_send(&Net_players[idx], data, packet_size);
}
// display any oo info on the hud
void multi_oo_display()
{
#ifndef NDEBUG
#endif
}
// ---------------------------------------------------------------------------------------------------
// DATARATE DEFINES/VARS
//
// low object update datarate limit
#define OO_LIMIT_LOW 1800
#define OO_LIMIT_MED 3400
#define OO_LIMIT_HIGH 100000000
// timestamp for sending control info (movement only - we'll send button info all the time)
#define OO_CIRATE 85 // 15x a second
int Multi_cirate_stamp = -1; // timestamp for waiting on control info time
int Multi_cirate_can_send = 1; // if we can send control info this frame
// global max rates
int OO_server_rate = -1; // max _total_ bandwidth to send to all clients
int OO_client_rate = -1; // max bandwidth to go to an individual client
// update timestamp for server datarate checking
#define RATE_UPDATE_TIME 1250 // in ms
int OO_server_rate_stamp = -1;
// bandwidth granularity
int OO_gran = 1;
DCF(oog, "Sets bandwidth granularity (Multiplayer)")
{
if (dc_optional_string_either("help", "--help")) {
dc_printf("Usage: oog <OO_gran>\n");
dc_printf("Sets bandwidth granularity\n");
return;
}
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf("Current Granularity is '%i' (default is 1)", OO_gran);
return;
}
dc_stuff_int(&OO_gran);
dc_printf("Ganularity set to %i", OO_gran);
}
// process datarate limiting stuff for the server
void multi_oo_server_process();
// process datarate limiting stuff for the client
void multi_oo_client_process();
// update the server datarate
void multi_oo_update_server_rate();
// ---------------------------------------------------------------------------------------------------
// DATARATE FUNCTIONS
//
// process all object update datarate details
void multi_oo_rate_process()
{
// if I have no valid player, drop out here
if(Net_player == NULL){
return;
}
// if we're not in mission, don't do anything
if(!(Game_mode & GM_IN_MISSION)){
return;
}
// if I'm the server of a game, process server stuff
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
multi_oo_server_process();
}
// otherwise process client-side stuff
else {
multi_oo_client_process();
}
}
// process datarate limiting stuff for the server
void multi_oo_server_process()
{
int idx;
// go through all players
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx]) && !MULTI_SERVER(Net_players[idx])){
// if his timestamp is -1 or has expired, reset it and zero his rate byte count
if((Net_players[idx].s_info.rate_stamp == -1) || timestamp_elapsed_safe(Net_players[idx].s_info.rate_stamp, OO_MAX_TIMESTAMP) || (abs(timestamp_ticker - Net_players[idx].s_info.rate_stamp) >= (int)(1000.0f / (float)OO_gran)) ){
Net_players[idx].s_info.rate_stamp = timestamp( (int)(1000.0f / (float)OO_gran) );
Net_players[idx].s_info.rate_bytes = 0;
}
}
}
// determine if we should be updating the server datarate
if((OO_server_rate_stamp == -1) || timestamp_elapsed_safe(OO_server_rate_stamp, OO_MAX_TIMESTAMP)){
// reset the timestamp
OO_server_rate_stamp = timestamp(RATE_UPDATE_TIME);
// update the server datarate
multi_oo_update_server_rate();
// nprintf(("Network","UPDATING SERVER DATARATE\n"));
}
}
// process datarate limiting stuff for the client
void multi_oo_client_process()
{
// if the timestamp is -1 or has elapsed, reset it
if((Multi_cirate_stamp == -1) || timestamp_elapsed_safe(Multi_cirate_stamp, OO_CIRATE)){
Multi_cirate_can_send = 1;
Multi_cirate_stamp = timestamp(OO_CIRATE);
}
}
// datarate limiting system for server -------------------------------------
// initialize the rate limiting system for all players
void multi_oo_rate_init_all()
{
int idx;
// if I don't have a net_player, bail here
if(Net_player == NULL){
return;
}
// if I'm the server of the game
if(Net_player->flags & NETINFO_FLAG_AM_MASTER){
// go through all players
for(idx=0;idx<MAX_PLAYERS;idx++){
if(MULTI_CONNECTED(Net_players[idx])){
multi_oo_rate_init(&Net_players[idx]);
}
}
OO_server_rate_stamp = -1;
}
// if i'm the client, initialize my control info datarate stuff
else {
Multi_cirate_stamp = -1;
Multi_cirate_can_send = 1;
}
}
// initialize the rate limiting for the passed in player
void multi_oo_rate_init(net_player *pl)
{
// reinitialize his datarate timestamp
pl->s_info.rate_stamp = -1;
pl->s_info.rate_bytes = 0;
}
// if the given net-player has exceeded his datarate limit
int multi_oo_rate_exceeded(net_player *pl)
{
int rate_compare;
// check against the guy's object update level
switch(pl->p_info.options.obj_update_level){
// low update level
case OBJ_UPDATE_LOW:
// the low object update limit
rate_compare = OO_LIMIT_LOW;
break;
// medium update level
case OBJ_UPDATE_MEDIUM:
// the low object update limit
rate_compare = OO_LIMIT_MED;
break;
// high update level - super high datarate (no capping, just intelligent updating)
case OBJ_UPDATE_HIGH:
rate_compare = OO_LIMIT_HIGH;
break;
// LAN - no rate max
case OBJ_UPDATE_LAN:
return 0;
// default level
default:
Int3();
rate_compare = OO_LIMIT_LOW;
break;
}
// if the server global rate PER CLIENT (OO_client_rate) is actually lower
if(OO_client_rate < rate_compare){
rate_compare = OO_client_rate;
}
// compare his bytes sent against the allowable amount
if(pl->s_info.rate_bytes >= rate_compare){
return 1;
}
// we're allowed to send
return 0;
}
// if it is ok for me to send a control info (will be ~N times a second)
int multi_oo_cirate_can_send()
{
// if we're allowed to send
if(Multi_cirate_can_send){
Multi_cirate_can_send = 0;
return 1;
}
return 0;
}
// dynamically update the server capped bandwidth rate
void multi_oo_update_server_rate()
{
int num_connections;
// bail conditions
if((Net_player == NULL) || !(Net_player->flags & NETINFO_FLAG_AM_MASTER)){
return;
}
// get the # of connections
num_connections = multi_num_connections();
if(!(Game_mode & GM_STANDALONE_SERVER)){
num_connections--;
}
// make sure we always pretend there's at least one guy available
if(num_connections <= 0){
num_connections = 1;
}
// set the data rate
switch(Net_player->p_info.options.obj_update_level){
// LAN update level
case OBJ_UPDATE_LAN:
// set to something super big so we don't limit anything
OO_server_rate = 500000000;
break;
// high update level
case OBJ_UPDATE_HIGH:
// set to 0 so we don't limit anything
OO_server_rate = Multi_options_g.datarate_cap;
break;
// medium update level
case OBJ_UPDATE_MEDIUM:
// set the rate to be "medium" update level
OO_server_rate = OO_LIMIT_MED;
break;
// low update level
case OBJ_UPDATE_LOW:
// set the rate to be the "low" update level
OO_server_rate = OO_LIMIT_LOW;
break;
default:
Int3();
return;
}
// set the individual client level
OO_client_rate = (int)(((float)OO_server_rate / (float)OO_gran) / (float)num_connections);
}
// reset all sequencing info (obsolete for new object update stuff)
void multi_oo_reset_sequencing()
{
}
// is this object one which needs to go through the interpolation
int multi_oo_is_interp_object(object *objp)
{
// if not multiplayer, skip it
if(!(Game_mode & GM_MULTIPLAYER)){
return 0;
}
// if its not a ship, skip it
if(objp->type != OBJ_SHIP){
return 0;
}
// other bogus cases
if((objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
return 0;
}
// if I'm a client and this is not me, I need to interp it
if(!MULTIPLAYER_MASTER){
if(objp != Player_obj){
return 1;
} else {
return 0;
}
}
// servers only interpolate other player ships
if(!(objp->flags & OF_PLAYER_SHIP)){
return 0;
}
// here we know its a player ship - is it mine?
if(objp == Player_obj){
return 0;
}
// interp it
return 1;
}
// interp
void multi_oo_interp(object *objp)
{
// make sure its a valid ship
Assert(Game_mode & GM_MULTIPLAYER);
if(objp->type != OBJ_SHIP){
return;
}
if((objp->instance < 0) || (objp->instance >= MAX_SHIPS)){
return;
}
// increment his approx "next" time
oo_arrive_time_next[objp->instance] += flFrametime;
// do stream weapon firing for this ship
Assert(objp != Player_obj);
if(objp != Player_obj){
ship_fire_primary(objp, 1, 0);
}
// if this ship doesn't have enough data points yet, skip it
if((oo_interp_count[objp->instance] < 2) || (oo_arrive_time_count[objp->instance] < 5)){
return;
}
// store the magnitude of his velocity
// float vel_mag = vm_vec_mag(&objp->phys_info.vel);
// determine how far along we are (0.0 to 1.0) until we should be getting the next packet
float t = oo_arrive_time_next[objp->instance] / oo_arrive_time_avg_diff[objp->instance];
// gr_set_color_fast(&Color_bright);
// gr_printf(100, 10, "%f\n", t);
// we've overshot. hmm. just keep the sim running I guess
if(t > 1.0f){
physics_sim(&objp->pos, &objp->orient, &objp->phys_info, flFrametime);
return;
}
// otherwise, blend the two curves together to get the new point
float u = 0.5f + (t * 0.5f);
vec3d p_bad, p_good;
oo_interp_splines[objp->instance][0].bez_get_point(&p_bad, u);
oo_interp_splines[objp->instance][1].bez_get_point(&p_good, u);
vm_vec_scale(&p_good, t);
vm_vec_scale(&p_bad, 1.0f - t);
vm_vec_add(&objp->pos, &p_bad, &p_good);
// set new velocity
// vm_vec_sub(&objp->phys_info.vel, &objp->pos, &objp->last_pos);
// run the sim for rotation
physics_sim_rot(&objp->orient, &objp->phys_info, flFrametime);
// blend velocity vectors together with an average weight
/*
vec3d v_bad, v_good;
oo_interp_splines[objp->instance][0].herm_get_deriv(&v_bad, u, 0);
oo_interp_splines[objp->instance][1].herm_get_deriv(&v_good, u, 0);
// t -= 1.0f;
vm_vec_scale(&v_good, t);
vm_vec_scale(&v_bad, 1.0f - t);
vm_vec_avg(&objp->phys_info.vel, &v_bad, &v_good);
// run the sim
physics_sim(&objp->pos, &objp->orient, &objp->phys_info, flFrametime);
*/
/*
vec3d v_bad, v_good;
oo_interp_splines[objp->instance][0].herm_get_point(&v_bad, u, 0);
oo_interp_splines[objp->instance][1].herm_get_point(&v_good, u, 0);
// t -= 1.0f;
vm_vec_scale(&v_good, t);
vm_vec_scale(&v_bad, 1.0f - t);
vm_vec_avg(&objp->pos, &v_bad, &v_good);
// run the sim
// physics_sim(&objp->pos, &objp->orient, &objp->phys_info, flFrametime);
physics_sim_rot(&objp->orient, &objp->phys_info, flFrametime);
*/
}
float oo_error = 0.8f;
DCF(oo_error, "Sets error factor for flight path prediction physics (Multiplayer)")
{
if (dc_optional_string_either("help", "--help")) {
dc_printf("Usage: oo_error <value>\n");
return;
}
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf("oo_error is currently %f", oo_error);
return;
}
dc_stuff_float(&oo_error);
dc_printf("oo_error set to %f", oo_error);
}
void multi_oo_calc_interp_splines(int ship_index, vec3d *cur_pos, matrix *cur_orient, physics_info *cur_phys_info, vec3d *new_pos, matrix *new_orient, physics_info *new_phys_info)
{
vec3d a, b, c;
matrix m_copy;
physics_info p_copy;
vec3d *pts[3] = {&a, &b, &c};
// average time between packets
float avg_diff = oo_arrive_time_avg_diff[ship_index];
// would this cause us to rubber-band?
vec3d v_norm = cur_phys_info->vel;
vec3d v_dir;
vm_vec_sub(&v_dir, new_pos, cur_pos);
if(!IS_VEC_NULL_SQ_SAFE(&v_norm) && !IS_VEC_NULL_SQ_SAFE(&v_dir)){
vm_vec_normalize(&v_dir);
vm_vec_normalize(&v_norm);
if(vm_vec_dotprod(&v_dir, &v_norm) < 0.0f){
*new_pos = *cur_pos;
}
}
// get the spline representing our "bad" movement. its better to be little bit off than to overshoot altogether
a = oo_interp_points[ship_index][0];
b = *cur_pos;
c = *cur_pos;
m_copy = *cur_orient;
p_copy = *cur_phys_info;
physics_sim(&c, &m_copy, &p_copy, avg_diff * oo_error); // next point, assuming we followed our current path
oo_interp_splines[ship_index][0].bez_set_points(3, pts);
// get the spline representing where this new point tells us we'd be heading
a = oo_interp_points[ship_index][0]; //-V519
b = oo_interp_points[ship_index][1]; //-V519
c = oo_interp_points[ship_index][1];
m_copy = *new_orient;
p_copy = *new_phys_info;
physics_sim(&c, &m_copy, &p_copy, avg_diff); // next point, given this new info
oo_interp_splines[ship_index][1].bez_set_points(3, pts);
// now we've got a spline representing our "new" path and where we would've gone had we been perfect before
// we'll modify our velocity to move along a blend of these splines.
}
void oo_update_time()
{
}
int display_oo_bez = 0;
DCF(bez, "Toggles rendering of player ship trajectory interpolation splines (Multiplayer) *disabled*")
{
if (dc_optional_string_either("status", "--status") || dc_optional_string_either("?", "--?")) {
dc_printf("Rendering of interpolation splines is '%s'", display_oo_bez ? "ON" : "OFF");
return;
}
display_oo_bez = !display_oo_bez;
dc_printf("%showing interp splines", display_oo_bez ? "S" : "Not s");
}
void oo_display()
{
/* int idx;
gr_set_color_fast(&Color_bright);
for(idx=0; idx<MAX_SHIPS; idx++){
// invalid ship
if(Ships[idx].objnum < 0){
continue;
}
// time between updates
if( (oo_arrive_time_count[idx] == 5) && (idx != (Player_ship - Ships)) ){
gr_printf(20, 40, "avg time between updates : %f", oo_arrive_time_avg_diff[idx]);
}
// interpolation splines
if( (oo_interp_count[idx] == 2) && (display_oo_bez) ){
oo_interp_splines[idx][0].bez_render(10, &Color_bright_red); // bad path
oo_interp_splines[idx][1].bez_render(10, &Color_bright_green); // good path
}
}
*/
}