/* * 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 "PreProcDefines.h" #ifndef NO_NETWORK #include "network/multi_oo.h" #include "globalincs/globals.h" #include "freespace2/freespace.h" #include "io/timer.h" #include "globalincs/linklist.h" #include "network/multimsgs.h" #include "network/multiutil.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" // --------------------------------------------------------------------------------------------------- // 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); // 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;idxm_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_types) && (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; } // 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 memcpy(data, &out_flags, sizeof(ubyte)); packet_size++; // 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 memcpy(data + packet_size, &tnet_signature, sizeof(ushort)); packet_size += sizeof(ushort); memcpy(data + packet_size, &t_subsys, sizeof(char)); packet_size += sizeof(char); memcpy(data + packet_size, &l_subsys, sizeof(char)); packet_size += sizeof(char); 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++; } 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){ // Int3(); 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 ); memcpy(data + packet_size + header_bytes, &percent, sizeof(char)); packet_size += 1; // 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); PACK_PERCENT(temp); multi_rate_add(NET_PLAYER_NUM(pl), "hul", 1); float quad = get_max_shield_quad(objp); // pack 2 shield values into each byte // pack quadrant 1 temp = (objp->shield_quadrant[0] / quad); PACK_PERCENT(temp); // pack quadrant 2 temp = (objp->shield_quadrant[1] / quad); PACK_PERCENT(temp); // pack quadrant 3 temp = (objp->shield_quadrant[2] / quad); PACK_PERCENT(temp); // pack quadrant 2 temp = (objp->shield_quadrant[3] / quad); PACK_PERCENT(temp); multi_rate_add(NET_PLAYER_NUM(pl), "shl", 4); } // 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; memcpy(data + packet_size + header_bytes, &ns, sizeof(ubyte)); packet_size++; 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; memcpy(data + packet_size + header_bytes, &ns, sizeof(ubyte)); packet_size++; 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; } memcpy(data + packet_size + header_bytes, &umode, sizeof(ubyte)); packet_size++; memcpy(data + packet_size + header_bytes, &submode, sizeof(short)); packet_size += 2; memcpy(data + packet_size + header_bytes, &target_signature, sizeof(ushort)); packet_size += 2; 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; memcpy(data + packet_size + header_bytes, &support_extra, sizeof(ubyte)); packet_size += 1; memcpy(data + packet_size + header_bytes, &Ai_info[shipp->ai_index].ai_flags, sizeof(int)); packet_size += 4; memcpy(data + packet_size + header_bytes, &Ai_info[shipp->ai_index].mode, sizeof(int)); packet_size += 4; memcpy(data + packet_size + header_bytes, &Ai_info[shipp->ai_index].submode, sizeof(int)); packet_size += 4; 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; } memcpy(data + packet_size + header_bytes, &dock_sig, sizeof(ushort)); packet_size += 2; } else { support_extra = 0; memcpy(data + packet_size + header_bytes, &support_extra, sizeof(ubyte)); packet_size += 1; } // make sure we have a valid chunk of data Assert(packet_size < 255); if(packet_size >= 255){ 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; int offset = 0; memcpy(&in_flags, data, sizeof(ubyte)); offset++; // get the player ship shipp = NULL; if((pl->m_player->objnum >= 0) && (Objects[pl->m_player->objnum].type == OBJ_SHIP) && (Objects[pl->m_player->objnum].instance >= 0)){ shipp = &Ships[Objects[pl->m_player->objnum].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; } } } // 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_DATA( 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_types)){ 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; } } // 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 shield_0, shield_1, shield_2, shield_3; // unpack the 4 quadrants UNPACK_PERCENT(shield_0); UNPACK_PERCENT(shield_1); UNPACK_PERCENT(shield_2); UNPACK_PERCENT(shield_3); float quad = get_max_shield_quad(pobjp); pobjp->shield_quadrant[0] = (shield_0 * quad); pobjp->shield_quadrant[1] = (shield_1 * quad); pobjp->shield_quadrant[2] = (shield_2 * quad); pobjp->shield_quadrant[3] = (shield_3 * 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 ); shipp->subsys_info[subsys_type].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_DATA(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){ // maybe turn them on if(!(pobjp->phys_info.flags & PF_AFTERBURNER_ON)){ afterburners_start(pobjp); } } 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; int ship_index; 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; ship_index = &Ships[obj->instance] - Ships; } 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); 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 | SIF_SHIP_CLASS_STEALTH))){ // 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, (char*)(&obj->pos), sizeof(vec3d)); cur_orient_chksum = cf_add_chksum_short(cur_orient_chksum, (char*)(&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; object *pobj; // if the player has an invalid objnum.. if(pl->m_player->objnum < 0){ return; } // get the player's object pobj = &Objects[pl->m_player->objnum]; 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; while((OO_ship_index[idx] >= 0) && (idx < MAX_SHIPS)){ // 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; idxobjnum >= 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; // if this is processed on the server, its a client object update packet player_index = -1; if(Net_player->flags & NETINFO_FLAG_AM_MASTER){ // determine what player this came from player_index = find_player_id(hinfo->id); if(player_index != -1){ pl = &Net_players[player_index]; } else { pl = NULL; } } // 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_idxnp_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; idxflags & 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); } } // 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 // 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, "") { dc_get_arg(ARG_INT); OO_gran = Dc_arg_int; } // 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= (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;idxs_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 = 100000000; 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, "") { dc_get_arg(ARG_FLOAT); oo_error = Dc_arg_float; } 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; // vec3d da, db, dc; matrix m_copy; physics_info p_copy; vec3d *pts[3] = {&a, &b, &c}; // vec3d *d_pts[3] = {&da, &db, &dc}; // 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(&v_norm) && !IS_VEC_NULL(&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]; b = oo_interp_points[ship_index][1]; 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); // get the spline for our "bad" movement /* a = oo_interp_points[ship_index][0]; b = *cur_pos; da = oo_interp_vel[ship_index][0]; db = cur_phys_info->vel; oo_interp_splines[ship_index][0].herm_set_points(2, pts, d_pts); // get the spline for our "good" movement a = oo_interp_points[ship_index][0]; b = oo_interp_points[ship_index][1]; da = oo_interp_vel[ship_index][0]; db = oo_interp_vel[ship_index][1]; oo_interp_splines[ship_index][1].herm_set_points(2, pts, d_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, "") { display_oo_bez = !display_oo_bez; if(display_oo_bez){ dc_printf("Showing interp splines"); } else { dc_printf("Not showing interp splines"); } } void oo_display() { /* int idx; gr_set_color_fast(&Color_bright); for(idx=0; idx