12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397139813991400140114021403140414051406140714081409141014111412141314141415141614171418141914201421142214231424142514261427142814291430143114321433143414351436143714381439144014411442144314441445144614471448144914501451145214531454145514561457145814591460146114621463146414651466146714681469147014711472147314741475147614771478147914801481148214831484148514861487148814891490149114921493149414951496149714981499150015011502150315041505150615071508150915101511151215131514151515161517151815191520152115221523152415251526152715281529153015311532153315341535153615371538153915401541154215431544154515461547154815491550155115521553155415551556155715581559156015611562156315641565156615671568156915701571157215731574157515761577157815791580158115821583158415851586158715881589159015911592159315941595159615971598159916001601160216031604160516061607160816091610161116121613161416151616161716181619162016211622162316241625162616271628162916301631163216331634163516361637163816391640164116421643164416451646164716481649165016511652165316541655165616571658165916601661166216631664166516661667166816691670167116721673167416751676167716781679168016811682168316841685168616871688168916901691169216931694169516961697169816991700170117021703170417051706170717081709171017111712171317141715171617171718171917201721172217231724172517261727172817291730173117321733173417351736173717381739174017411742174317441745174617471748174917501751175217531754175517561757175817591760176117621763176417651766176717681769177017711772177317741775177617771778177917801781178217831784178517861787178817891790179117921793179417951796179717981799180018011802180318041805180618071808180918101811181218131814181518161817181818191820182118221823182418251826182718281829183018311832183318341835183618371838183918401841184218431844184518461847184818491850185118521853185418551856185718581859186018611862186318641865186618671868186918701871187218731874187518761877187818791880188118821883188418851886188718881889189018911892189318941895189618971898189919001901190219031904190519061907190819091910191119121913191419151916191719181919192019211922192319241925192619271928192919301931193219331934193519361937193819391940194119421943194419451946194719481949195019511952195319541955195619571958195919601961196219631964196519661967196819691970197119721973197419751976197719781979198019811982198319841985198619871988198919901991199219931994199519961997199819992000200120022003200420052006200720082009201020112012201320142015201620172018201920202021202220232024202520262027202820292030203120322033203420352036203720382039204020412042204320442045204620472048204920502051205220532054205520562057205820592060206120622063206420652066206720682069207020712072207320742075207620772078207920802081208220832084208520862087208820892090209120922093209420952096209720982099210021012102210321042105210621072108210921102111211221132114211521162117211821192120212121222123212421252126212721282129213021312132213321342135213621372138213921402141214221432144214521462147214821492150215121522153215421552156215721582159216021612162216321642165216621672168216921702171217221732174217521762177217821792180218121822183218421852186218721882189219021912192219321942195219621972198219922002201220222032204220522062207220822092210221122122213221422152216221722182219222022212222222322242225222622272228222922302231223222332234223522362237223822392240224122422243224422452246224722482249225022512252225322542255225622572258225922602261226222632264226522662267226822692270227122722273227422752276227722782279228022812282228322842285228622872288228922902291229222932294229522962297229822992300230123022303230423052306230723082309231023112312231323142315231623172318231923202321232223232324232523262327232823292330233123322333233423352336233723382339234023412342234323442345234623472348234923502351235223532354235523562357235823592360236123622363236423652366236723682369237023712372237323742375237623772378237923802381238223832384238523862387238823892390239123922393239423952396239723982399240024012402240324042405240624072408240924102411241224132414241524162417241824192420242124222423242424252426242724282429243024312432243324342435243624372438243924402441244224432444244524462447244824492450245124522453245424552456245724582459246024612462246324642465246624672468246924702471247224732474247524762477247824792480248124822483248424852486248724882489249024912492249324942495249624972498249925002501250225032504250525062507250825092510251125122513251425152516251725182519252025212522252325242525252625272528252925302531253225332534253525362537253825392540254125422543254425452546254725482549255025512552255325542555255625572558255925602561256225632564256525662567256825692570257125722573257425752576257725782579258025812582258325842585258625872588258925902591259225932594259525962597259825992600260126022603260426052606260726082609261026112612261326142615261626172618261926202621262226232624262526262627262826292630263126322633263426352636263726382639264026412642264326442645264626472648264926502651265226532654265526562657265826592660266126622663266426652666266726682669267026712672267326742675267626772678267926802681268226832684268526862687268826892690269126922693269426952696269726982699270027012702270327042705270627072708270927102711271227132714271527162717271827192720272127222723272427252726272727282729273027312732273327342735273627372738273927402741274227432744274527462747274827492750275127522753275427552756275727582759276027612762276327642765276627672768276927702771277227732774277527762777277827792780278127822783278427852786278727882789279027912792279327942795279627972798279928002801280228032804280528062807280828092810281128122813281428152816281728182819282028212822282328242825282628272828282928302831283228332834283528362837283828392840284128422843284428452846284728482849285028512852285328542855285628572858285928602861286228632864286528662867286828692870287128722873287428752876287728782879288028812882288328842885288628872888288928902891289228932894289528962897289828992900290129022903290429052906290729082909291029112912291329142915291629172918291929202921292229232924292529262927292829292930293129322933293429352936293729382939294029412942294329442945294629472948294929502951295229532954295529562957295829592960296129622963296429652966296729682969297029712972297329742975297629772978297929802981298229832984298529862987298829892990299129922993299429952996299729982999300030013002300330043005300630073008300930103011301230133014301530163017301830193020302130223023302430253026302730283029303030313032303330343035303630373038303930403041304230433044304530463047304830493050305130523053305430553056305730583059306030613062306330643065306630673068306930703071307230733074307530763077307830793080308130823083308430853086308730883089309030913092309330943095309630973098309931003101310231033104310531063107310831093110311131123113311431153116311731183119312031213122312331243125312631273128312931303131313231333134313531363137313831393140314131423143314431453146314731483149315031513152315331543155315631573158315931603161316231633164316531663167316831693170317131723173317431753176317731783179318031813182318331843185318631873188318931903191319231933194319531963197319831993200320132023203320432053206320732083209321032113212321332143215321632173218321932203221322232233224322532263227322832293230323132323233323432353236323732383239324032413242324332443245324632473248324932503251325232533254325532563257325832593260326132623263326432653266326732683269327032713272327332743275327632773278327932803281328232833284328532863287328832893290329132923293329432953296329732983299330033013302330333043305330633073308330933103311331233133314331533163317331833193320332133223323332433253326332733283329333033313332333333343335333633373338333933403341334233433344334533463347334833493350335133523353335433553356335733583359336033613362336333643365336633673368336933703371337233733374337533763377337833793380338133823383338433853386338733883389339033913392339333943395339633973398339934003401340234033404340534063407340834093410341134123413341434153416341734183419342034213422342334243425342634273428342934303431343234333434343534363437343834393440344134423443344434453446344734483449345034513452345334543455345634573458345934603461346234633464346534663467346834693470347134723473347434753476347734783479348034813482348334843485348634873488348934903491349234933494349534963497349834993500350135023503350435053506350735083509351035113512351335143515351635173518351935203521352235233524352535263527352835293530353135323533353435353536353735383539354035413542354335443545354635473548354935503551355235533554355535563557355835593560356135623563356435653566356735683569357035713572357335743575357635773578357935803581358235833584358535863587358835893590359135923593359435953596359735983599360036013602360336043605360636073608360936103611361236133614361536163617361836193620362136223623362436253626362736283629363036313632363336343635363636373638363936403641364236433644364536463647364836493650365136523653365436553656365736583659366036613662366336643665366636673668366936703671367236733674367536763677367836793680368136823683368436853686368736883689369036913692369336943695369636973698369937003701370237033704370537063707370837093710371137123713371437153716371737183719372037213722372337243725372637273728372937303731373237333734373537363737373837393740374137423743374437453746374737483749375037513752375337543755375637573758375937603761376237633764376537663767376837693770377137723773377437753776377737783779378037813782378337843785378637873788378937903791379237933794379537963797379837993800380138023803380438053806380738083809381038113812381338143815381638173818381938203821382238233824382538263827382838293830383138323833383438353836383738383839384038413842384338443845384638473848384938503851385238533854385538563857385838593860386138623863386438653866386738683869387038713872387338743875387638773878387938803881388238833884388538863887388838893890389138923893389438953896389738983899390039013902390339043905390639073908390939103911391239133914391539163917391839193920392139223923392439253926392739283929393039313932393339343935393639373938393939403941394239433944394539463947394839493950395139523953395439553956395739583959396039613962396339643965396639673968396939703971397239733974397539763977397839793980398139823983398439853986398739883989399039913992399339943995399639973998399940004001400240034004400540064007400840094010401140124013401440154016401740184019402040214022402340244025402640274028402940304031403240334034403540364037403840394040404140424043404440454046404740484049405040514052405340544055405640574058405940604061406240634064406540664067406840694070407140724073407440754076407740784079408040814082408340844085408640874088408940904091409240934094409540964097409840994100410141024103410441054106410741084109411041114112411341144115411641174118411941204121412241234124412541264127412841294130413141324133413441354136413741384139414041414142 |
- /*
- ===========================================================================
- Doom 3 BFG Edition GPL Source Code
- Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company.
- This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code").
- Doom 3 BFG Edition Source Code is free software: you can redistribute it and/or modify
- it under the terms of the GNU General Public License as published by
- the Free Software Foundation, either version 3 of the License, or
- (at your option) any later version.
- Doom 3 BFG Edition Source Code is distributed in the hope that it will be useful,
- but WITHOUT ANY WARRANTY; without even the implied warranty of
- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- GNU General Public License for more details.
- You should have received a copy of the GNU General Public License
- along with Doom 3 BFG Edition Source Code. If not, see <http://www.gnu.org/licenses/>.
- In addition, the Doom 3 BFG Edition Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 BFG Edition Source Code. If not, please request a copy in writing from id Software at the address below.
- If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA.
- ===========================================================================
- */
- #pragma hdrstop
- #include "../idlib/precompiled.h"
- #include "sys_lobby.h"
- extern idCVar net_connectTimeoutInSeconds;
- extern idCVar net_headlessServer;
- idCVar net_checkVersion( "net_checkVersion", "0", CVAR_INTEGER, "Check for matching version when clients connect. 0: normal rules, 1: force check, otherwise no check (pass always)" );
- idCVar net_peerTimeoutInSeconds( "net_peerTimeoutInSeconds", "30", CVAR_INTEGER, "If the host hasn't received a response from a peer in this amount of time (in seconds), the peer will be disconnected." );
- idCVar net_peerTimeoutInSeconds_Lobby( "net_peerTimeoutInSeconds_Lobby", "20", CVAR_INTEGER, "If the host hasn't received a response from a peer in this amount of time (in seconds), the peer will be disconnected." );
- // NOTE - The snapshot exchange does the bandwidth challenge
- idCVar net_bw_challenge_enable( "net_bw_challenge_enable", "0", CVAR_BOOL, "Enable pre game bandwidth challenge for throttling snap rate" );
- idCVar net_bw_test_interval( "net_bw_test_interval", "33", CVAR_INTEGER, "MS - how often to send packets in bandwidth test" );
- idCVar net_bw_test_numPackets( "net_bw_test_numPackets", "30", CVAR_INTEGER, "Number of bandwidth challenge packets to send" );
- idCVar net_bw_test_packetSizeBytes( "net_bw_test_packetSizeBytes", "1024", CVAR_INTEGER, "Size of each packet to send out" );
- idCVar net_bw_test_timeout( "net_bw_test_timeout", "500", CVAR_INTEGER, "MS after receiving a bw test packet that client will time out" );
- idCVar net_bw_test_host_timeout( "net_bw_test_host_timeout", "3000", CVAR_INTEGER, "How long host will wait in MS to hear bw results from peers" );
- idCVar net_bw_test_throttle_rate_pct( "net_bw_test_throttle_rate_pct", "0.80", CVAR_FLOAT, "Min rate % a peer must match in bandwidth challenge before being throttled. 1.0=perfect, 0.0=received nothing" );
- idCVar net_bw_test_throttle_byte_pct( "net_bw_test_throttle_byte_pct", "0.80", CVAR_FLOAT, "Min byte % a peer must match in bandwidth challenge before being throttled. 1.0=perfect (received everything) 0.0=Received nothing" );
- idCVar net_bw_test_throttle_seq_pct( "net_bw_test_throttle_seq_pct", "0.80", CVAR_FLOAT, "Min sequence % a peer must match in bandwidth test before being throttled. 1.0=perfect. This score will be more adversely affected by packet loss than byte %" );
- idCVar net_ignoreConnects( "net_ignoreConnects", "0", CVAR_INTEGER, "Test as if no one can connect to me. 0 = off, 1 = ignore with no reply, 2 = send goodbye" );
- idCVar net_skipGoodbye( "net_skipGoodbye", "0", CVAR_BOOL, "" );
- extern unsigned long NetGetVersionChecksum();
- /*
- ========================
- idLobby::idLobby
- ========================
- */
- idLobby::idLobby() {
- lobbyType = TYPE_INVALID;
- sessionCB = NULL;
- localReadSS = NULL;
- objMemory = NULL;
- haveSubmittedSnaps = false;
- state = STATE_IDLE;
- failedReason = FAILED_UNKNOWN;
- host = -1;
- peerIndexOnHost = -1;
- isHost = false;
- needToDisplayMigrateMsg = false;
- migrateMsgFlags = 0;
-
- partyToken = 0; // will be initialized later
- loaded = false;
- respondToArbitrate = false;
- waitForPartyOk = false;
- startLoadingFromHost = false;
-
- nextSendPingValuesTime = 0;
- lastPingValuesRecvTime = 0;
- nextSendMigrationGameTime = 0;
- nextSendMigrationGamePeer = 0;
- bandwidthChallengeStartTime = 0;
- bandwidthChallengeEndTime = 0;
- bandwidthChallengeFinished = false;
- bandwidthChallengeNumGoodSeq = 0;
- lastSnapBspHistoryUpdateSequence = -1;
- assert( userList.Max() == freeUsers.Max() );
- assert( userList.Max() == userPool.Max() );
- userPool.SetNum( userPool.Max() );
- assert( freeUsers.Num() == 0 );
- assert( freeUsers.Num() == 0 );
- // Initialize free user list
- for ( int i = 0; i < userPool.Num(); i++ ) {
- freeUsers.Append( &userPool[i] );
- }
-
- showHostLeftTheSession = false;
- connectIsFromInvite = false;
- }
- /*
- ========================
- idLobby::Initialize
- ========================
- */
- void idLobby::Initialize( lobbyType_t sessionType_, idSessionCallbacks * callbacks ) {
- assert( callbacks != NULL );
- lobbyType = sessionType_;
- sessionCB = callbacks;
- if ( lobbyType == GetActingGameStateLobbyType() ) {
- // only needed in multiplayer mode
- objMemory = (uint8*)Mem_Alloc( SNAP_OBJ_JOB_MEMORY, TAG_NETWORKING );
- lzwData = (lzwCompressionData_t*)Mem_Alloc( sizeof( lzwCompressionData_t ), TAG_NETWORKING );
- }
- }
- //===============================================================================
- // ** BEGIN PUBLIC INTERFACE ***
- //===============================================================================
- /*
- ========================
- idLobby::StartHosting
- ========================
- */
- void idLobby::StartHosting( const idMatchParameters & parms_ ) {
- parms = parms_;
- // Allow common to modify the parms
- common->OnStartHosting( parms );
- Shutdown(); // Make sure we're in a shutdown state before proceeding
- assert( GetNumLobbyUsers() == 0 );
- assert( lobbyBackend == NULL );
- // Get the skill level of all the players that will eventually go into the lobby
- StartCreating();
- }
- /*
- ========================
- idLobby::StartFinding
- ========================
- */
- void idLobby::StartFinding( const idMatchParameters & parms_ ) {
- parms = parms_;
- Shutdown(); // Make sure we're in a shutdown state before proceeding
- assert( GetNumLobbyUsers() == 0 );
- assert( lobbyBackend == NULL );
- // Clear search results
- searchResults.Clear();
- lobbyBackend = sessionCB->FindLobbyBackend( parms, sessionCB->GetPartyLobby().GetNumLobbyUsers(), sessionCB->GetPartyLobby().GetAverageSessionLevel(), idLobbyBackend::TYPE_GAME );
- SetState( STATE_SEARCHING );
- }
- /*
- ========================
- idLobby::Pump
- ========================
- */
- void idLobby::Pump() {
-
- // Check the heartbeat of all our peers, make sure we shouldn't disconnect from peers that haven't sent a heartbeat in awhile
- CheckHeartBeats();
- UpdateHostMigration();
- UpdateLocalSessionUsers();
- switch ( state ) {
- case STATE_IDLE: State_Idle(); break;
- case STATE_CREATE_LOBBY_BACKEND: State_Create_Lobby_Backend(); break;
- case STATE_SEARCHING: State_Searching(); break;
- case STATE_OBTAINING_ADDRESS: State_Obtaining_Address(); break;
- case STATE_CONNECT_HELLO_WAIT: State_Connect_Hello_Wait(); break;
- case STATE_FINALIZE_CONNECT: State_Finalize_Connect(); break;
- case STATE_FAILED: break;
- default:
- idLib::Error( "idLobby::Pump: Unknown state." );
- }
- }
- /*
- ========================
- idLobby::ProcessSnapAckQueue
- ========================
- */
- void idLobby::ProcessSnapAckQueue() {
- SCOPED_PROFILE_EVENT( "ProcessSnapAckQueue" );
- const int SNAP_ACKS_TO_PROCESS_PER_FRAME = 1;
- int numProcessed = 0;
- while ( snapDeltaAckQueue.Num() > 0 && numProcessed < SNAP_ACKS_TO_PROCESS_PER_FRAME ) {
- if ( ApplySnapshotDeltaInternal( snapDeltaAckQueue[0].p, snapDeltaAckQueue[0].snapshotNumber ) ) {
- numProcessed++;
- }
- snapDeltaAckQueue.RemoveIndex( 0 );
- }
- }
- /*
- ========================
- idLobby::Shutdown
- ========================
- */
- void idLobby::Shutdown( bool retainMigrationInfo, bool skipGoodbye ) {
- // Cancel host migration if we were in the process of it and this is the session type that was migrating
- if ( !retainMigrationInfo && migrationInfo.state != MIGRATE_NONE ) {
- idLib::Printf( "Cancelling host migration on %s.\n", GetLobbyName() );
- EndMigration();
- }
- failedReason = FAILED_UNKNOWN;
- if ( lobbyBackend == NULL ) {
- NET_VERBOSE_PRINT( "NET: ShutdownLobby (already shutdown) (%s)\n", GetLobbyName() );
- // If we don't have this lobbyBackend type, we better be properly shutdown for this lobby
- assert( GetNumLobbyUsers() == 0 );
- assert( host == -1 );
- assert( peerIndexOnHost == -1 );
- assert( !isHost );
- assert( lobbyType != GetActingGameStateLobbyType() || !loaded );
- assert( lobbyType != GetActingGameStateLobbyType() || !respondToArbitrate );
- assert( snapDeltaAckQueue.Num() == 0 );
- // Make sure we don't have old peers connected to this lobby
- for ( int p = 0; p < peers.Num(); p++ ) {
- assert( peers[p].GetConnectionState() == CONNECTION_FREE );
- }
- state = STATE_IDLE;
- return;
- }
- NET_VERBOSE_PRINT( "NET: ShutdownLobby (%s)\n", GetLobbyName() );
-
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( peers[p].GetConnectionState() != CONNECTION_FREE ) {
- SetPeerConnectionState( p, CONNECTION_FREE, skipGoodbye ); // This will send goodbye's
- }
- }
-
- // Remove any users that weren't handled in ResetPeers
- // (this will happen as a client, because we won't get the reliable msg from the server since we are severing the connection)
- for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
- lobbyUser_t * user = GetLobbyUser( i );
- UnregisterUser( user );
- }
- FreeAllUsers();
- host = -1;
- peerIndexOnHost = -1;
- isHost = false;
- needToDisplayMigrateMsg = false;
- migrationDlg = GDM_INVALID;
- partyToken = 0; // Reset our party token so we recompute
- loaded = false;
- respondToArbitrate = false;
- waitForPartyOk = false;
- startLoadingFromHost = false;
- snapDeltaAckQueue.Clear();
- // Shutdown the lobbyBackend
- if ( !retainMigrationInfo ) {
- sessionCB->DestroyLobbyBackend( lobbyBackend );
- lobbyBackend = NULL;
- }
- state = STATE_IDLE;
- }
- /*
- ========================
- idLobby::HandlePacket
- ========================
- */
- void idLobby::HandlePacket( lobbyAddress_t & remoteAddress, idBitMsg fragMsg, idPacketProcessor::sessionId_t sessionID ) {
- SCOPED_PROFILE_EVENT( "HandlePacket" );
- // msg will hold a fully constructed msg using the packet processor
- byte msgBuffer[ idPacketProcessor::MAX_MSG_SIZE ];
- idBitMsg msg;
- msg.InitWrite( msgBuffer, sizeof( msgBuffer ) );
- int peerNum = FindPeer( remoteAddress, sessionID );
- int type = idPacketProcessor::RETURN_TYPE_NONE;
- int userData = 0;
- if ( peerNum >= 0 ) {
- if ( !peers[peerNum].IsActive() ) {
- idLib::Printf( "NET: Received in-band packet from peer %s with no active connection.\n", remoteAddress.ToString() );
- return;
- }
- type = peers[ peerNum ].packetProc->ProcessIncoming( Sys_Milliseconds(), peers[peerNum].sessionID, fragMsg, msg, userData, peerNum );
- } else {
- if ( !idPacketProcessor::ProcessConnectionlessIncoming( fragMsg, msg, userData ) ) {
- idLib::Printf( "ProcessConnectionlessIncoming FAILED from %s.\n", remoteAddress.ToString() );
- // Not a valid connectionless packet
- return;
- }
-
- // Valid connectionless packets are always RETURN_TYPE_OOB
- type = idPacketProcessor::RETURN_TYPE_OOB;
-
- // Find the peer this connectionless msg should go to
- peerNum = FindPeer( remoteAddress, sessionID, true );
- }
- if ( type == idPacketProcessor::RETURN_TYPE_NONE ) {
- // This packet is not necessarily invalid, it could be a start or middle of a fragmented packet that's not fully constructed.
- return;
- }
- if ( peerNum >= 0 ) {
- // Update their heart beat (only if we've received a valid packet (we've checked type == idPacketProcessor::RETURN_TYPE_NONE))
- peers[peerNum].lastHeartBeat = Sys_Milliseconds();
- }
- // Handle server query requests. We do this before the STATE_IDLE check. This is so we respond.
- // We may want to change this to just ignore the request if we are idle, and change the timeout time
- // on the requesters part to just timeout faster.
- if ( type == idPacketProcessor::RETURN_TYPE_OOB ) {
- if ( userData == OOB_MATCH_QUERY || userData == OOB_SYSTEMLINK_QUERY ) {
- sessionCB->HandleServerQueryRequest( remoteAddress, msg, userData );
- return;
- }
- if ( userData == OOB_MATCH_QUERY_ACK ) {
- sessionCB->HandleServerQueryAck( remoteAddress, msg );
- return;
- }
- }
- if ( type == idPacketProcessor::RETURN_TYPE_OOB ) {
- if ( userData == OOB_VOICE_AUDIO ) {
- sessionCB->HandleOobVoiceAudio( remoteAddress, msg );
- } else if ( userData == OOB_HELLO ) {
- // Handle new peer connect request
- peerNum = HandleInitialPeerConnection( msg, remoteAddress, peerNum );
- return;
- } else if ( userData == OOB_MIGRATE_INVITE ) {
- NET_VERBOSE_PRINT( "NET: Migration invite for session %s from %s (state = %s)\n", GetLobbyName(), remoteAddress.ToString(), session->GetStateString() );
- // Get connection info
- lobbyConnectInfo_t connectInfo;
- connectInfo.ReadFromMsg( msg );
- if ( lobbyBackend != NULL && lobbyBackend->GetState() != idLobbyBackend::STATE_FAILED && lobbyBackend->IsOwnerOfConnectInfo( connectInfo ) ) { // Ignore duplicate invites
- idLib::Printf( "NET: Already migrated to %s.\n", remoteAddress.ToString() );
- return;
- }
- if ( migrationInfo.state == MIGRATE_NONE ) {
- if ( IsPeer() && host >= 0 && host < peers.Num() && Sys_Milliseconds() - peers[host].lastHeartBeat > 8 * 1000 ) {
- // Force migration early if we get an invite, and it has been some time since we've heard from the host
- PickNewHost();
- } else {
- idLib::Printf( "NET: Ignoring migration invite because we are not migrating %s\n", remoteAddress.ToString() );
- SendGoodbye( remoteAddress ); // So they can remove us from their invite list
- return;
- }
- }
- if ( !sessionCB->PreMigrateInvite( *this ) ) {
- NET_VERBOSE_PRINT( "NET: sessionCB->PreMigrateInvite( *this ) failed from %s\n", remoteAddress.ToString() );
- return;
- }
- // If we are also becoming a new host, see who wins
- if ( migrationInfo.state == MIGRATE_BECOMING_HOST ) {
- int inviteIndex = FindMigrationInviteIndex( remoteAddress );
- if ( inviteIndex != -1 ) {
- // We found them in our list, check to make sure our ping is better
- int ping1 = migrationInfo.ourPingMs;
- lobbyUserID_t userId1 = migrationInfo.ourUserId;
- int ping2 = migrationInfo.invites[inviteIndex].pingMs;
- lobbyUserID_t userId2 = migrationInfo.invites[inviteIndex].userId;
- if ( IsBetterHost( ping1, userId1, ping2, userId2 ) ) {
- idLib::Printf( "NET: Ignoring migration invite from %s, since our ping is better (%i / %i).\n", remoteAddress.ToString(), ping1, ping2 );
- return;
- }
- }
- }
- bool fromGame = msg.ReadBool();
- // Kill the current lobbyBackend
- Shutdown();
- // Connect to the lobby
- ConnectTo( connectInfo, true ); // Pass in true for the invite flag, so we can connect to invite only lobby if we need to
- if ( verify( sessionCB != NULL ) ) {
- if ( sessionCB->BecomingPeer( *this ) ) {
- migrationInfo.persistUntilGameEndsData.wasMigratedJoin = true;
- migrationInfo.persistUntilGameEndsData.wasMigratedGame = fromGame;
- }
- }
- } else if ( userData == OOB_GOODBYE || userData == OOB_GOODBYE_W_PARTY || userData == OOB_GOODBYE_FULL ) {
- HandleGoodbyeFromPeer( peerNum, remoteAddress, userData );
- return;
- } else if ( userData == OOB_RESOURCE_LIST ) {
- if ( !verify( lobbyType == GetActingGameStateLobbyType() ) ) {
- return;
- }
- if ( peerNum != host ) {
- NET_VERBOSE_PRINT( "NET: Resource list from non-host %i, %s\n", peerNum, remoteAddress.ToString() );
- return;
- }
- if ( peerNum >= 0 && !peers[peerNum].IsConnected() ) {
- NET_VERBOSE_PRINT( "NET: Resource list from host with no game connection: %i, %s\n", peerNum, remoteAddress.ToString() );
- return;
- }
- } else if ( userData == OOB_BANDWIDTH_TEST ) {
- int seqNum = msg.ReadLong();
- // TODO: We should read the random data and verify the MD5 checksum
- int time = Sys_Milliseconds();
- bool inOrder = ( seqNum == 0 || peers[peerNum].bandwidthSequenceNum + 1 == seqNum );
- int timeSinceLast = 0;
- if ( bandwidthChallengeStartTime <= 0 ) {
- // Reset the test
- NET_VERBOSE_PRINT( "\nNET: Starting bandwidth test @ %d\n", time );
- bandwidthChallengeStartTime = time;
- peers[peerNum].bandwidthSequenceNum = 0;
- peers[peerNum].bandwidthTestBytes = peers[peerNum].packetProc->GetIncomingBytes();
- } else {
- timeSinceLast = time - (bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) );
- }
- if ( inOrder ) {
- bandwidthChallengeNumGoodSeq++;
- }
- bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() );
- NET_VERBOSE_PRINT( " NET: %sRecevied OOB bandwidth test %d delta time: %d incoming rate: %.2f incoming rate 2: %d\n", inOrder ? "^2" : "^1", seqNum, timeSinceLast, peers[peerNum].packetProc->GetIncomingRateBytes(), peers[peerNum].packetProc->GetIncomingRate2() );
- peers[peerNum].bandwidthSequenceNum = seqNum;
- } else {
- NET_VERBOSE_PRINT( "NET: Unknown oob packet %d from %s (%d)\n", userData, remoteAddress.ToString(), peerNum );
- }
- } else if ( type == idPacketProcessor::RETURN_TYPE_INBAND ) {
- // Process in-band message
- if ( peerNum < 0 ) {
- idLib::Printf( "NET: In-band message from unknown peer: %s\n", remoteAddress.ToString() );
- return;
- }
-
- if ( !verify( peers[ peerNum ].address.Compare( remoteAddress ) ) ) {
- idLib::Printf( "NET: Peer with wrong address: %i, %s\n", peerNum, remoteAddress.ToString() );
- return;
- }
- // Handle reliable
- int numReliable = peers[ peerNum ].packetProc->GetNumReliables();
- for ( int r = 0; r < numReliable; r++ ) {
- // Just in case one of the reliable msg's cause this peer to disconnect
- // (this can happen when our party/game host is the same, he quits the game lobby, and sends a reliable msg for us to leave the game)
- peerNum = FindPeer( remoteAddress, sessionID );
- if ( peerNum == -1 ) {
- idLib::Printf( "NET: Dropped peer while processing reliable msg's: %i, %s\n", peerNum, remoteAddress.ToString() );
- break;
- }
- const byte * reliableData = peers[ peerNum ].packetProc->GetReliable( r );
- int reliableSize = peers[ peerNum ].packetProc->GetReliableSize( r );
- idBitMsg reliableMsg( reliableData, reliableSize );
- reliableMsg.SetSize( reliableSize );
- HandleReliableMsg( peerNum, reliableMsg );
- }
-
- if ( peerNum == -1 || !peers[ peerNum ].IsConnected() ) {
- // If the peer still has no connection after HandleReliableMsg, then something is wrong.
- // (We could have been in CONNECTION_CONNECTING state for this session type, but the first message
- // we should receive from the server is the ack, otherwise, something went wrong somewhere)
- idLib::Printf( "NET: In-band message from host with no active connection: %i, %s\n", peerNum, remoteAddress.ToString() );
- return;
- }
- // Handle unreliable part (if any)
- if ( msg.GetRemainingData() > 0 && loaded ) {
- if ( !verify( lobbyType == GetActingGameStateLobbyType() ) ) {
- idLib::Printf( "NET: Snapshot msg for non game session lobby %s\n", remoteAddress.ToString() );
- return;
- }
-
- if ( peerNum == host ) {
- idSnapShot localSnap;
- int sequence = -1;
- int baseseq = -1;
- bool fullSnap = false;
- localReadSS = &localSnap;
- // If we are the peer, we assume we only receive snapshot data on the in-band channel
- const byte * deltaData = msg.GetReadData() + msg.GetReadCount();
- int deltaLength = msg.GetRemainingData();
- if ( peers[ peerNum ].snapProc->ReceiveSnapshotDelta( deltaData, deltaLength, 0, sequence, baseseq, localSnap, fullSnap ) ) {
-
- NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Got %s snapshot %d delta'd against %d. SS Time: %d\n", ( fullSnap ? "partial" : "full" ), sequence, baseseq, localSnap.GetTime() ) );
-
- if ( sessionCB->GetState() != idSession::INGAME && sequence != -1 ) {
- int seq = peers[ peerNum ].snapProc->GetLastAppendedSequence();
- // When we aren't in the game, we need to send this as reliable msg's, since usercmds won't be taking care of it for us
- byte ackbuffer[32];
- idBitMsg ackmsg( ackbuffer, sizeof( ackbuffer ) );
- ackmsg.WriteLong( seq );
- // Add incoming BPS for QoS
- float incomingBPS = peers[ peerNum ].receivedBps;
- if ( peers[ peerNum ].receivedBpsIndex != seq ) {
- incomingBPS = idMath::ClampFloat( 0.0f, static_cast<float>( idLobby::BANDWIDTH_REPORTING_MAX ), peers[host].packetProc->GetIncomingRateBytes() );
- peers[ peerNum ].receivedBpsIndex = seq;
- peers[ peerNum ].receivedBps = incomingBPS;
- }
- ackmsg.WriteQuantizedUFloat< idLobby::BANDWIDTH_REPORTING_MAX, idLobby::BANDWIDTH_REPORTING_BITS >( incomingBPS );
- QueueReliableMessage( host, RELIABLE_SNAPSHOT_ACK, ackbuffer, sizeof( ackbuffer ) );
- }
- }
- if ( fullSnap ) {
- sessionCB->ReceivedFullSnap();
- common->NetReceiveSnapshot( localSnap );
- }
- localReadSS = NULL;
-
- } else {
- // If we are the host, we assume we only receive usercmds on the inband channel
- int snapNum = 0;
- uint16 receivedBps_quantized = 0;
- byte usercmdBuffer[idPacketProcessor::MAX_FINAL_PACKET_SIZE];
- lzwCompressionData_t lzwData;
- idLZWCompressor lzwCompressor( &lzwData );
- lzwCompressor.Start( const_cast<byte *>( msg.GetReadData() ) + msg.GetReadCount(), msg.GetRemainingData() );
- lzwCompressor.ReadAgnostic( snapNum );
- lzwCompressor.ReadAgnostic( receivedBps_quantized );
- int usercmdSize = lzwCompressor.Read( usercmdBuffer, sizeof( usercmdBuffer ), true );
- lzwCompressor.End();
- float receivedBps = ( receivedBps_quantized / (float)( BIT( idLobby::BANDWIDTH_REPORTING_BITS ) - 1 ) ) * (float)idLobby::BANDWIDTH_REPORTING_MAX;
- if ( peers[ peerNum ].receivedBpsIndex != snapNum ) {
- peers[ peerNum ].receivedBps = receivedBps;
- peers[ peerNum ].receivedBpsIndex = snapNum;
- }
- if ( snapNum < 50 ) {
- NET_VERBOSE_PRINT( "NET: peer %d ack'd snapNum %d\n", peerNum, snapNum );
- }
- ApplySnapshotDelta( peerNum, snapNum );
- idBitMsg usercmdMsg( (const byte *)usercmdBuffer, usercmdSize );
- common->NetReceiveUsercmds( peerNum, usercmdMsg );
- }
- }
- }
- }
- /*
- ========================
- idLobby::HasActivePeers
- ========================
- */
- bool idLobby::HasActivePeers() const {
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( peers[p].GetConnectionState() != CONNECTION_FREE ) {
- return true;
- }
- }
-
- return false;
- }
- /*
- ========================
- idLobby::NumFreeSlots
- ========================
- */
- int idLobby::NumFreeSlots() const {
- if ( parms.matchFlags & MATCH_JOIN_IN_PROGRESS ) {
- return parms.numSlots - GetNumConnectedUsers();
- } else {
- return parms.numSlots - GetNumLobbyUsers();
- }
- }
- //===============================================================================
- // ** END PUBLIC INTERFACE ***
- //===============================================================================
- //===============================================================================
- // ** BEGIN STATE CODE ***
- //===============================================================================
- const char * idLobby::stateToString[ NUM_STATES ] = {
- ASSERT_ENUM_STRING( STATE_IDLE, 0 ),
- ASSERT_ENUM_STRING( STATE_CREATE_LOBBY_BACKEND, 1 ),
- ASSERT_ENUM_STRING( STATE_SEARCHING, 2 ),
- ASSERT_ENUM_STRING( STATE_OBTAINING_ADDRESS, 3 ),
- ASSERT_ENUM_STRING( STATE_CONNECT_HELLO_WAIT, 4 ),
- ASSERT_ENUM_STRING( STATE_FINALIZE_CONNECT, 5 ),
- ASSERT_ENUM_STRING( STATE_FAILED, 6 ),
- };
- /*
- ========================
- idLobby::State_Idle
- ========================
- */
- void idLobby::State_Idle() {
- // If lobbyBackend is in a failed state, shutdown, go to a failed state ourself, and return
- if ( lobbyBackend != NULL && lobbyBackend->GetState() == idLobbyBackend::STATE_FAILED ) {
- HandleConnectionAttemptFailed();
- common->Dialog().ClearDialog( GDM_MIGRATING );
- common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
- common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
- return;
- }
- if ( migrationInfo.persistUntilGameEndsData.hasGameData && sessionCB->GetState() <= idSession::IDLE ) {
- // This can happen with 'leaveGame' or 'disconnect' since those paths don't go through endMatch
- // This seems like an ok catch all place but there may be a better way to handle this
- ResetAllMigrationState();
- common->Dialog().ClearDialog( GDM_MIGRATING );
- common->Dialog().ClearDialog( GDM_MIGRATING_WAITING );
- common->Dialog().ClearDialog( GDM_MIGRATING_RELAUNCHING );
- }
- }
- /*
- ========================
- idLobby::State_Create_Lobby_Backend
- ========================
- */
- void idLobby::State_Create_Lobby_Backend() {
- if ( !verify( lobbyBackend != NULL ) ) {
- SetState( STATE_FAILED );
- return;
- }
- assert( lobbyBackend != NULL );
- if ( migrationInfo.state == MIGRATE_BECOMING_HOST ) {
- const int DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS = session->GetTitleStorageInt( "DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS", 30 );
- // If we are taking too long, cancel the connection
- if ( DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS > 0 ) {
- if ( Sys_Milliseconds() - migrationInfo.migrationStartTime > 1000 * DETECT_SERVICE_DISCONNECT_TIMEOUT_IN_SECONDS ) {
- SetState( STATE_FAILED );
- return;
- }
- }
- }
- if ( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING ) {
- return; // Busy but valid
- }
- if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
- SetState( STATE_FAILED );
- return;
- }
- // Success
- InitStateLobbyHost();
-
- // Set state to idle to signify to session we are done creating
- SetState( STATE_IDLE );
- }
- /*
- ========================
- idLobby::State_Searching
- ========================
- */
- void idLobby::State_Searching() {
- if ( !verify( lobbyBackend != NULL ) ) {
- SetState( STATE_FAILED );
- return;
- }
- if ( lobbyBackend->GetState() == idLobbyBackend::STATE_SEARCHING ) {
- return; // Busy but valid
- }
- if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
- SetState( STATE_FAILED ); // Any other lobbyBackend state is invalid
- return;
- }
-
- // Done searching, get results from lobbyBackend
- lobbyBackend->GetSearchResults( searchResults );
- if ( searchResults.Num() == 0 ) {
- // If we didn't get any results, set state to failed
- SetState( STATE_FAILED );
- return;
- }
- extern idCVar net_maxSearchResultsToTry;
- const int maxSearchResultsToTry = session->GetTitleStorageInt( "net_maxSearchResultsToTry", net_maxSearchResultsToTry.GetInteger() );
- if ( searchResults.Num() > maxSearchResultsToTry ) {
- searchResults.SetNum( maxSearchResultsToTry );
- }
- // Set state to idle to signify we are done searching
- SetState( STATE_IDLE );
- }
- /*
- ========================
- idLobby::State_Obtaining_Address
- ========================
- */
- void idLobby::State_Obtaining_Address() {
- if ( lobbyBackend->GetState() == idLobbyBackend::STATE_OBTAINING_ADDRESS ) {
- return; // Valid but not ready
- }
-
- if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
- // There was an error, signify to caller
- failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED;
- NET_VERBOSE_PRINT("idLobby::State_Obtaining_Address: the lobby backend failed." );
- SetState( STATE_FAILED );
- return;
- }
- //
- // We have the address of the lobbyBackend, we can now send a hello packet
- //
- // This will be the host for this lobby type
- host = AddPeer( hostAddress, GenerateSessionID() );
- // Record start time of connection attempt to the host
- helloStartTime = Sys_Milliseconds();
- lastConnectRequest = helloStartTime;
- connectionAttempts = 0;
- // Change state to connecting
- SetState( STATE_CONNECT_HELLO_WAIT );
- // Send first connect attempt now (we'll send more periodically if we fail to receive an ack)
- // (we do this after changing state, since the function expects we're in the right state)
- SendConnectionRequest();
- }
- /*
- ========================
- idLobby::State_Finalize_Connect
- ========================
- */
- void idLobby::State_Finalize_Connect() {
- if ( lobbyBackend->GetState() == idLobbyBackend::STATE_CREATING ) {
- // Valid but busy
- return;
- }
- if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
- // Any other state not valid, failed
- SetState( STATE_FAILED );
- return;
- }
-
- // Success
- SetState( STATE_IDLE );
- // Tell session mgr if this was a migration
- if ( migrationInfo.persistUntilGameEndsData.wasMigratedJoin ) {
- sessionCB->BecamePeer( *this );
- }
- }
- /*
- ========================
- idLobby::State_Connect_Hello_Wait
- ========================
- */
- void idLobby::State_Connect_Hello_Wait() {
- if ( lobbyBackend->GetState() != idLobbyBackend::STATE_READY ) {
- // If the lobbyBackend is in an error state, shut everything down
- NET_VERBOSE_PRINT( "NET: Lobby is no longer ready while waiting for lobbyType %s hello.\n", GetLobbyName() );
- HandleConnectionAttemptFailed();
- return;
- }
- int time = Sys_Milliseconds();
-
- const int timeoutMs = session->GetTitleStorageInt( "net_connectTimeoutInSeconds", net_connectTimeoutInSeconds.GetInteger() ) * 1000;
- if ( timeoutMs != 0 && time - helloStartTime > timeoutMs ) {
- NET_VERBOSE_PRINT( "NET: Timeout waiting for lobbyType %s for party hello.\n", GetLobbyName() );
- HandleConnectionAttemptFailed();
- return;
- }
- if ( connectionAttempts < MAX_CONNECT_ATTEMPTS ) {
- assert( connectionAttempts >= 1 ); // Should have at least the initial connection attempt
- // See if we need to send another hello request
- // (keep getting more frequent to increase chance due to possible packet loss, but clamp to MIN_CONNECT_FREQUENCY seconds)
- // TODO: We could eventually make timing out a function of actual number of attempts rather than just plain time.
- int resendTime = Max( MIN_CONNECT_FREQUENCY_IN_SECONDS, CONNECT_REQUEST_FREQUENCY_IN_SECONDS / connectionAttempts ) * 1000;
-
- if ( time - lastConnectRequest > resendTime ) {
- SendConnectionRequest();
- lastConnectRequest = time;
- }
- }
- }
- /*
- ========================
- idLobby::SetState
- ========================
- */
- void idLobby::SetState( lobbyState_t newState ) {
- assert( newState < NUM_STATES );
- assert( state < NUM_STATES );
- verify_array_size( stateToString, NUM_STATES );
- if ( state == newState ) {
- NET_VERBOSE_PRINT( "NET: idLobby::SetState: State SAME %s for session %s\n", stateToString[ newState ], GetLobbyName() );
- return;
- }
- // Set the current state
- NET_VERBOSE_PRINT( "NET: idLobby::SetState: State changing from %s to %s for session %s\n", stateToString[ state ], stateToString[ newState ], GetLobbyName() );
- state = newState;
- }
- //===============================================================================
- // ** END STATE CODE ***
- //===============================================================================
- /*
- ========================
- idLobby::StartCreating
- ========================
- */
- void idLobby::StartCreating() {
- assert( lobbyBackend == NULL );
- assert( state == STATE_IDLE );
- float skillLevel = GetAverageLocalUserLevel( true );
- lobbyBackend = sessionCB->CreateLobbyBackend( parms, skillLevel, (idLobbyBackend::lobbyBackendType_t)lobbyType );
- SetState( STATE_CREATE_LOBBY_BACKEND );
- }
- /*
- ========================
- idLobby::FindPeer
- ========================
- */
- int idLobby::FindPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID, bool ignoreSessionID ) {
- bool connectionless = ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY ||
- sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME ||
- sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE );
-
- if ( connectionless && !ignoreSessionID ) {
- return -1; // This was meant to be connectionless. FindPeer is meant for connected (or connecting) peers
- }
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( peers[p].GetConnectionState() == CONNECTION_FREE ) {
- continue;
- }
- if ( peers[p].address.Compare( remoteAddress ) ) {
- if ( connectionless && ignoreSessionID ) {
- return p;
- }
- // Using a rolling check, so that we account for possible packet loss, and out of order issues
- if ( IsPeer() ) {
- idPacketProcessor::sessionId_t searchStart = peers[p].sessionID;
-
- // Since we only roll the code between matches, we should only need to look ahead a couple increments.
- // Worse case, if the stars line up, the client doesn't see the new sessionId, and times out, and gets booted.
- // This should be impossible though, since the timings won't be possible considering how long it takes to end the match,
- // and restart, and then restart again.
- int numTries = 2;
- while ( numTries-- > 0 && searchStart != sessionID ) {
- searchStart = IncrementSessionID( searchStart );
- if ( searchStart == sessionID ) {
- idLib::Printf( "NET: Rolling session ID check found new ID: %i\n", searchStart );
- if ( peers[p].packetProc != NULL ) {
- peers[p].packetProc->VerifyEmptyReliableQueue( RELIABLE_GAME_DATA, RELIABLE_DUMMY_MSG );
- }
- peers[p].sessionID = searchStart;
- break;
- }
- }
- }
- if ( peers[p].sessionID != sessionID ) {
- continue;
- }
- return p;
- }
- }
- return -1;
- }
- /*
- ========================
- idLobby::FindAnyPeer
- Find a peer when we don't know the session id, and we don't care since it's a connectionless msg
- ========================
- */
- int idLobby::FindAnyPeer( const lobbyAddress_t & remoteAddress ) const {
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( peers[p].GetConnectionState() == CONNECTION_FREE ) {
- continue;
- }
- if ( peers[p].address.Compare( remoteAddress ) ) {
- return p;
- }
- }
- return -1;
- }
- /*
- ========================
- idLobby::FindFreePeer
- ========================
- */
- int idLobby::FindFreePeer() const {
-
- // Return the first non active peer
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( !peers[p].IsActive() ) {
- return p;
- }
- }
- return -1;
- }
- /*
- ========================
- idLobby::AddPeer
- ========================
- */
- int idLobby::AddPeer( const lobbyAddress_t & remoteAddress, idPacketProcessor::sessionId_t sessionID ) {
- // First, make sure we don't already have this peer
- int p = FindPeer( remoteAddress, sessionID );
- assert( p == -1 ); // When using session ID's, we SHOULDN'T find this remoteAddress/sessionID combo
- if ( p == -1 ) {
- // If we didn't find the peer, we need to add a new one
-
- p = FindFreePeer();
- if ( p == -1 ) {
- peer_t newPeer;
- p = peers.Append( newPeer );
- }
-
- peer_t & peer = peers[p];
-
- peer.ResetAllData();
-
- assert( peer.connectionState == CONNECTION_FREE );
- peer.address = remoteAddress;
- peer.sessionID = sessionID;
- NET_VERBOSE_PRINT( "NET: Added peer %s at index %i\n", remoteAddress.ToString(), p );
- } else {
- NET_VERBOSE_PRINT( "NET: Found peer %s at index %i\n", remoteAddress.ToString(), p );
- }
- SetPeerConnectionState( p, CONNECTION_CONNECTING );
-
- if ( lobbyType == GetActingGameStateLobbyType() ) {
- // Reset various flags used in game mode
- peers[p].ResetMatchData();
- }
- return p;
- }
- /*
- ========================
- idLobby::DisconnectPeerFromSession
- ========================
- */
- void idLobby::DisconnectPeerFromSession( int p ) {
- if ( !verify( IsHost() ) ) {
- return;
- }
-
- peer_t & peer = peers[p];
- if ( peer.GetConnectionState() != CONNECTION_FREE ) {
- SetPeerConnectionState( p, CONNECTION_FREE );
- }
- }
- /*
- ========================
- idLobby::DisconnectAllPeers
- ========================
- */
- void idLobby::DisconnectAllPeers() {
- for ( int p = 0; p < peers.Num(); p++ ) {
- DisconnectPeerFromSession( p );
- }
- }
- /*
- ========================
- idLobby::SendGoodbye
- ========================
- */
- void idLobby::SendGoodbye( const lobbyAddress_t & remoteAddress, bool wasFull ) {
- if ( net_skipGoodbye.GetBool() ) {
- return;
- }
- NET_VERBOSE_PRINT( "NET: Sending goodbye to %s for %s (wasFull = %i)\n", remoteAddress.ToString(), GetLobbyName(), wasFull );
- static const int NUM_REDUNDANT_GOODBYES = 10;
-
- int msgType = OOB_GOODBYE;
- if ( wasFull ) {
- msgType = OOB_GOODBYE_FULL;
- } else if ( lobbyType == TYPE_GAME && ( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) && !( parms.matchFlags & MATCH_PARTY_INVITE_PLACEHOLDER ) ) {
- msgType = OOB_GOODBYE_W_PARTY;
- }
-
- for ( int i = 0; i < NUM_REDUNDANT_GOODBYES; i++ ) {
- SendConnectionLess( remoteAddress, msgType );
- }
- }
- /*
- ========================
- idLobby::SetPeerConnectionState
- ========================
- */
- void idLobby::SetPeerConnectionState( int p, connectionState_t newState, bool skipGoodbye ) {
- if ( !verify( p >= 0 && p < peers.Num() ) ) {
- idLib::Printf( "NET: SetPeerConnectionState invalid peer index %i\n", p );
- return;
- }
- peer_t & peer = peers[p];
- const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType();
- if ( peer.GetConnectionState() == newState ) {
- idLib::Printf( "NET: SetPeerConnectionState: Peer already in state %i\n", newState );
- assert( 0 ); // This case means something is most likely bad, and it's the programmers fault
- assert( ( peer.packetProc != NULL ) == peer.IsActive() );
- assert( ( ( peer.snapProc != NULL ) == peer.IsActive() ) == ( actingGameStateLobbyType == lobbyType ) );
- return;
- }
- if ( newState == CONNECTION_CONNECTING ) {
- //mem.PushHeap();
- // We better be coming from a free connection state if we are trying to connect
- assert( peer.GetConnectionState() == CONNECTION_FREE );
- assert( peer.packetProc == NULL );
- peer.packetProc = new ( TAG_NETWORKING )idPacketProcessor();
- if ( lobbyType == actingGameStateLobbyType ) {
- assert( peer.snapProc == NULL );
- peer.snapProc = new ( TAG_NETWORKING )idSnapshotProcessor();
- }
- //mem.PopHeap();
- } else if ( newState == CONNECTION_ESTABLISHED ) {
- // If we are marking this peer as connected for the first time, make sure this peer was actually trying to connect.
- assert( peer.GetConnectionState() == CONNECTION_CONNECTING );
- } else if ( newState == CONNECTION_FREE ) {
- // If we are freeing this connection and we had an established connection before, make sure to send a goodbye
- if ( peer.GetConnectionState() == CONNECTION_ESTABLISHED && !skipGoodbye ) {
- idLib::Printf("SetPeerConnectionState: Sending goodbye to peer %s from session %s\n", peer.address.ToString(), GetLobbyName() );
- SendGoodbye( peer.address );
- }
- }
- peer.connectionState = newState;
- if ( !peer.IsActive() ) {
- if ( peer.packetProc != NULL ) {
- delete peer.packetProc;
- peer.packetProc = NULL;
- }
- if ( peer.snapProc != NULL ) {
- assert( lobbyType == actingGameStateLobbyType );
- delete peer.snapProc;
- peer.snapProc = NULL;
- }
- }
- // Do this in case we disconnected the peer
- if ( IsHost() ) {
- RemoveUsersWithDisconnectedPeers();
- }
- }
- /*
- ========================
- idLobby::QueueReliableMessage
- ========================
- */
- void idLobby::QueueReliableMessage( int p, byte type, const byte * data, int dataLen ) {
- if ( !verify( p >= 0 && p < peers.Num() ) ) {
- return;
- }
- peer_t & peer = peers[p];
-
- if ( !peer.IsConnected() ) {
- // Don't send to this peer if we don't have an established connection of this session type
- NET_VERBOSE_PRINT( "NET: Not sending reliable type %i to peer %i because connectionState is %i\n", type, p, peer.GetConnectionState() );
- return;
- }
- if ( peer.packetProc->NumQueuedReliables() > 2 ) {
- idLib::PrintfIf( false, "NET: peer.packetProc->NumQueuedReliables() > 2: %i (%i / %s)\n", peer.packetProc->NumQueuedReliables(), p, peer.address.ToString() );
- }
- if ( !peer.packetProc->QueueReliableMessage( type, data, dataLen ) ) {
- // For now, when this happens, disconnect from all session types
- NET_VERBOSE_PRINT( "NET: Dropping peer because we overflowed his reliable message queue\n" );
- if ( IsHost() ) {
- // Disconnect peer from this session type
- DisconnectPeerFromSession( p );
- } else {
- Shutdown(); // Shutdown session if we can't queue the reliable
- }
- }
- }
- /*
- ========================
- idLobby::GetNumConnectedPeers
- ========================
- */
- int idLobby::GetNumConnectedPeers() const {
- int numConnected = 0;
- for ( int i = 0; i < peers.Num(); i++ ) {
- if ( peers[i].IsConnected() ) {
- numConnected++;
- }
- }
- return numConnected;
- }
- /*
- ========================
- idLobby::GetNumConnectedPeersInGame
- ========================
- */
- int idLobby::GetNumConnectedPeersInGame() const {
- int numActive = 0;
- for ( int i = 0; i < peers.Num(); i++ ) {
- if ( peers[i].IsConnected() && peers[i].inGame ) {
- numActive++;
- }
- }
- return numActive;
- }
- /*
- ========================
- idLobby::SendMatchParmsToPeers
- ========================
- */
- void idLobby::SendMatchParmsToPeers() {
- if ( !IsHost() ) {
- return;
- }
- if ( GetNumConnectedPeers() == 0 ) {
- return;
- }
- byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
- idBitMsg msg( buffer, sizeof( buffer ) );
- parms.Write( msg );
-
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( !peers[p].IsConnected() ) {
- continue;
- }
- QueueReliableMessage( p, RELIABLE_MATCH_PARMS, msg.GetReadData(), msg.GetSize() );
- }
- }
- /*
- ========================
- STATIC idLobby::IsReliablePlayerToPlayerType
- ========================
- */
- bool idLobby::IsReliablePlayerToPlayerType( byte type ) {
- return ( type >= RELIABLE_PLAYER_TO_PLAYER_BEGIN ) && ( type < RELIABLE_PLAYER_TO_PLAYER_END );
- }
- /*
- ========================
- idLobby::HandleReliablePlayerToPlayerMsg
- ========================
- */
- void idLobby::HandleReliablePlayerToPlayerMsg( int peerNum, idBitMsg & msg, int type ) {
- reliablePlayerToPlayerHeader_t info;
- int c, b;
- msg.SaveReadState( c, b ); // in case we need to forward or fail
- if ( !info.Read( this, msg ) ) {
- idLib::Warning( "NET: Ignoring invalid reliable player to player message" );
- msg.RestoreReadState( c, b );
- return;
- }
- const bool isForLocalPlayer = IsSessionUserIndexLocal( info.toSessionUserIndex );
- if ( isForLocalPlayer ) {
- HandleReliablePlayerToPlayerMsg( info, msg, type );
- } else if ( IsHost() ) {
- const int targetPeer = PeerIndexForSessionUserIndex( info.toSessionUserIndex );
- msg.RestoreReadState( c, b );
- // forward the rest of the data
- const byte * data = msg.GetReadData() + msg.GetReadCount();
- int dataLen = msg.GetSize() - msg.GetReadCount();
- QueueReliableMessage( targetPeer, type, data, dataLen );
- } else {
- idLib::Warning( "NET: Can't forward reliable message for remote player: I'm not the host" );
- }
- }
- /*
- ========================
- idLobby::HandleReliablePlayerToPlayerMsg
- ========================
- */
- void idLobby::HandleReliablePlayerToPlayerMsg( const reliablePlayerToPlayerHeader_t & info, idBitMsg & msg, int reliableType ) {
- #if 0
- // Remember that the reliablePlayerToPlayerHeader_t was already removed from the msg
- reliablePlayerToPlayer_t type = (reliablePlayerToPlayer_t)( reliableType - RELIABLE_PLAYER_TO_PLAYER_BEGIN );
- switch( type ) {
- case RELIABLE_PLAYER_TO_PLAYER_VOICE_EVENT: {
- sessionCB->HandleReliableVoiceEvent( *this, info.fromSessionUserIndex, info.toSessionUserIndex, msg );
- break;
- }
- default: {
- idLib::Warning( "NET: Ignored unknown player to player reliable type %i", (int) type );
- }
- };
- #endif
- }
- /*
- ========================
- idLobby::SendConnectionLess
- ========================
- */
- void idLobby::SendConnectionLess( const lobbyAddress_t & remoteAddress, byte type, const byte * data, int dataLen ) {
- idBitMsg msg( data, dataLen );
- msg.SetSize( dataLen );
- byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ];
- idBitMsg processedMsg( buffer, sizeof( buffer ) );
- // Process the send
- idPacketProcessor::ProcessConnectionlessOutgoing( msg, processedMsg, lobbyType, type );
- const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE );
- // Send it
- sessionCB->SendRawPacket( remoteAddress, processedMsg.GetReadData(), processedMsg.GetSize(), useDirectPort );
- }
- /*
- ========================
- idLobby::SendConnectionRequest
- ========================
- */
- void idLobby::SendConnectionRequest() {
- // Some sanity checking
- assert( state == STATE_CONNECT_HELLO_WAIT );
- assert( peers[host].GetConnectionState() == CONNECTION_CONNECTING );
- assert( GetNumLobbyUsers() == 0 );
- // Buffer to hold connect msg
- byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
- idBitMsg msg( buffer, sizeof( buffer ) );
- // Add the current version info to the handshake
- const unsigned long localChecksum = NetGetVersionChecksum();
-
- NET_VERBOSE_PRINT( "NET: version = %i\n", localChecksum );
- msg.WriteLong( localChecksum );
- msg.WriteUShort( peers[host].sessionID );
- msg.WriteBool( connectIsFromInvite );
- // We use InitSessionUsersFromLocalUsers here to copy the current local users over to session users simply to have a list
- // to send on the initial connection attempt. We immediately clear our session user list once sent.
- InitSessionUsersFromLocalUsers( true );
- if ( GetNumLobbyUsers() > 0 ) {
- // Fill up the msg with the users on this machine
- msg.WriteByte( GetNumLobbyUsers() );
- for ( int u = 0; u < GetNumLobbyUsers(); u++ ) {
- GetLobbyUser( u )->WriteToMsg( msg );
- }
- } else {
- FreeAllUsers();
- SetState( STATE_FAILED );
- return;
- }
- // We just used these users to fill up the msg above, we will get the real list from the server if we connect.
- FreeAllUsers();
- NET_VERBOSE_PRINT( "NET: Sending hello to: %s (lobbyType: %s, session ID %i, attempt: %i)\n", hostAddress.ToString(), GetLobbyName(), peers[host].sessionID, connectionAttempts );
- SendConnectionLess( hostAddress, OOB_HELLO, msg.GetReadData(), msg.GetSize() );
-
- connectionAttempts++;
- }
- /*
- ========================
- idLobby::ConnectTo
- Fires off a request to get the address of a lobbyBackend owner, and then attempts to connect (eventually handled in HandleObtainingLobbyOwnerAddress)
- ========================
- */
- void idLobby::ConnectTo( const lobbyConnectInfo_t & connectInfo, bool fromInvite ) {
- NET_VERBOSE_PRINT( "NET: idSessionLocal::ConnectTo: fromInvite = %i\n", fromInvite );
- // Make sure current session is shutdown
- Shutdown();
- connectIsFromInvite = fromInvite;
- lobbyBackend = sessionCB->JoinFromConnectInfo( connectInfo, (idLobbyBackend::lobbyBackendType_t)lobbyType );
- // First, we need the address of the lobbyBackend owner
- lobbyBackend->GetOwnerAddress( hostAddress );
- SetState( STATE_OBTAINING_ADDRESS );
- }
- /*
- ========================
- idLobby::HandleGoodbyeFromPeer
- ========================
- */
- void idLobby::HandleGoodbyeFromPeer( int peerNum, lobbyAddress_t & remoteAddress, int msgType ) {
- if ( migrationInfo.state != MIGRATE_NONE ) {
- // If this peer is on our invite list, remove them
- for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) {
- if ( migrationInfo.invites[i].address.Compare( remoteAddress, true ) ) {
- migrationInfo.invites.RemoveIndex( i );
- break;
- }
- }
- }
- if ( peerNum < 0 ) {
- NET_VERBOSE_PRINT( "NET: Goodbye from unknown peer %s on session %s\n", remoteAddress.ToString(), GetLobbyName() );
- return;
- }
- if ( peers[peerNum].GetConnectionState() == CONNECTION_FREE ) {
- NET_VERBOSE_PRINT( "NET: Goodbye from peer %s on session %s that is not connected\n", remoteAddress.ToString(), GetLobbyName() );
- return;
- }
- if ( IsHost() ) {
- // Goodbye from peer, remove him
- NET_VERBOSE_PRINT( "NET: Goodbye from peer %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() );
- DisconnectPeerFromSession( peerNum );
- } else {
- // Let session handler take care of this
- NET_VERBOSE_PRINT( "NET: Goodbye from host %s, on session %s\n", remoteAddress.ToString(), GetLobbyName() );
- sessionCB->GoodbyeFromHost( *this, peerNum, remoteAddress, msgType );
- }
- }
- /*
- ========================
- idLobby::HandleGoodbyeFromPeer
- ========================
- */
- void idLobby::HandleConnectionAttemptFailed() {
- Shutdown();
- failedReason = migrationInfo.persistUntilGameEndsData.wasMigratedJoin ? FAILED_MIGRATION_CONNECT_FAILED : FAILED_CONNECT_FAILED;
- SetState( STATE_FAILED );
- if ( migrationInfo.persistUntilGameEndsData.wasMigratedJoin ) {
- sessionCB->FailedGameMigration( *this );
- }
- ResetAllMigrationState();
- needToDisplayMigrateMsg = false;
- migrateMsgFlags = 0;
- }
- /*
- ========================
- idLobby::ConnectToNextSearchResult
- ========================
- */
- bool idLobby::ConnectToNextSearchResult() {
- if ( lobbyType != TYPE_GAME ) {
- return false; // Only game sessions use matchmaking searches
- }
- // End current session lobby (this WON'T free search results)
- Shutdown();
- if ( searchResults.Num() == 0 ) {
- return false; // No more search results to connect to, give up
- }
-
- // Get next search result
- lobbyConnectInfo_t connectInfo = searchResults[0];
- // Remove this search result
- searchResults.RemoveIndex( 0 );
- // If we are connecting to a game lobby, tell our party to connect to this lobby as well
- if ( lobbyType == TYPE_GAME && sessionCB->GetPartyLobby().IsLobbyActive() ) {
- sessionCB->GetPartyLobby().SendMembersToLobby( lobbyType, connectInfo, true );
- }
- // Attempt to connect the lobby
- ConnectTo( connectInfo, true ); // Pass in true for invite, since searches are for matchmaking, and we should always be able to connect to those types of matches
- // Clear the "Lobby was Full" dialog in case it's up, since we are going to try to connect to a different lobby now
- common->Dialog().ClearDialog( GDM_LOBBY_FULL );
- return true; // Notify caller we are attempting to connect
- }
- /*
- ========================
- idLobby::CheckVersion
- ========================
- */
- bool idLobby::CheckVersion( idBitMsg & msg, lobbyAddress_t peerAddress ) {
- const unsigned long remoteChecksum = msg.ReadLong();
- if ( net_checkVersion.GetInteger() == 1 ) {
- const unsigned long localChecksum = NetGetVersionChecksum();
- NET_VERBOSE_PRINT( "NET: Comparing handshake version - localChecksum = %i, remoteChecksum = %i\n", localChecksum, remoteChecksum );
- return ( remoteChecksum == localChecksum );
- }
- return true;
- }
- /*
- ========================
- idLobby::VerifyNumConnectingUsers
- Make sure number of users connecting is valid, and make sure we have enough room
- ========================
- */
- bool idLobby::VerifyNumConnectingUsers( idBitMsg & msg ) {
- int c, b;
- msg.SaveReadState( c, b );
- const int numUsers = msg.ReadByte();
- msg.RestoreReadState( c, b );
- const int numFreeSlots = NumFreeSlots();
- NET_VERBOSE_PRINT( "NET: VerifyNumConnectingUsers %i users, %i free slots for %s\n", numUsers, numFreeSlots, GetLobbyName() );
- if ( numUsers <= 0 || numUsers > MAX_PLAYERS - 1 ) {
- NET_VERBOSE_PRINT( "NET: Invalid numUsers %i\n", numUsers );
- return false;
- } else if ( numUsers > numFreeSlots ) {
- NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available\n", numUsers, numFreeSlots );
- return false;
- } else if ( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY && sessionCB->GetGameLobby().IsLobbyActive() && !IsMigrating() ) {
- const int numFreeGameSlots = sessionCB->GetGameLobby().NumFreeSlots();
- if ( numUsers > numFreeGameSlots ) {
- NET_VERBOSE_PRINT( "NET: %i slots requested, but only %i are available on the active game session\n", numUsers, numFreeGameSlots );
- return false;
- }
- }
- return true;
- }
- /*
- ========================
- idLobby::VerifyLobbyUserIDs
- ========================
- */
- bool idLobby::VerifyLobbyUserIDs( idBitMsg & msg ) {
- int c, b;
- msg.SaveReadState( c, b );
- const int numUsers = msg.ReadByte();
- // Add the new users to our own list
- for ( int u = 0; u < numUsers; u++ ) {
- lobbyUser_t newUser;
-
- // Read in the new user
- newUser.ReadFromMsg( msg );
- if ( GetLobbyUserIndexByID( newUser.lobbyUserID, true ) != -1 ) {
- msg.RestoreReadState( c, b );
- return false;
- }
- }
- msg.RestoreReadState( c, b );
- return true;
- }
- /*
- ========================
- idLobby::HandleInitialPeerConnection
- Received on an initial peer connect request (OOB_HELLO)
- ========================
- */
- int idLobby::HandleInitialPeerConnection( idBitMsg & msg, const lobbyAddress_t & peerAddress, int peerNum ) {
- if ( net_ignoreConnects.GetInteger() > 0 ) {
- if ( net_ignoreConnects.GetInteger() == 2 ) {
- SendGoodbye( peerAddress );
- }
- return -1;
- }
- if ( !IsHost() ) {
- NET_VERBOSE_PRINT( "NET: Got connectionless hello from peer %s on session, and we are not a host\n", peerAddress.ToString() );
- SendGoodbye( peerAddress );
- return -1;
- }
-
- // See if this is a peer migrating to us, if so, remove them from our invite list
- bool migrationInvite = false;
- int migrationGameData = -1;
-
- for ( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- ) {
- if ( migrationInfo.invites[i].address.Compare( peerAddress, true ) ) {
-
- migrationGameData = migrationInfo.invites[i].migrationGameData;
- migrationInfo.invites.RemoveIndex( i ); // Remove this peer from the list, since this peer will now be connected (or rejected, either way we don't want to keep sending invites)
- migrationInvite = true;
- NET_VERBOSE_PRINT( "^2NET: Response from migration invite %s. GameData: %d\n", peerAddress.ToString(), migrationGameData );
- }
- }
- if ( !MatchTypeIsJoinInProgress( parms.matchFlags ) && lobbyType == TYPE_GAME && migrationInfo.persistUntilGameEndsData.wasMigratedHost && IsMigratedStatsGame() && !migrationInvite ) {
- // No matter what, don't let people join migrated game sessions that are going to continue on to the same game
- // Not on invite list in a migrated game session - bounce him
- NET_VERBOSE_PRINT( "NET: Denying game connection from %s since not on migration invite list\n", peerAddress.ToString() );
- for ( int i = migrationInfo.invites.Num() - 1; i >= 0; i-- ) {
- NET_VERBOSE_PRINT( " Invite[%d] addr: %s\n", i, migrationInfo.invites[i].address.ToString() );
- }
- SendGoodbye( peerAddress );
- return -1;
- }
-
- if ( MatchTypeIsJoinInProgress( parms.matchFlags ) ) {
- // If this is for a game connection, make sure we have a game lobby
- if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() < idSession::GAME_LOBBY ) {
- NET_VERBOSE_PRINT( "NET: Denying game connection from %s because we don't have a game lobby\n", peerAddress.ToString() );
- SendGoodbye( peerAddress );
- return -1;
- }
- } else {
- // If this is for a game connection, make sure we are in the game lobby
- if ( lobbyType == TYPE_GAME && sessionCB->GetState() != idSession::GAME_LOBBY ) {
- NET_VERBOSE_PRINT( "NET: Denying game connection from %s while not in game lobby\n", peerAddress.ToString() );
- SendGoodbye( peerAddress );
- return -1;
- }
-
- // If this is for a party connection, make sure we are not in game, unless this was for host migration invite
- if ( !migrationInvite && lobbyType == TYPE_PARTY && ( sessionCB->GetState() == idSession::INGAME || sessionCB->GetState() == idSession::LOADING ) ) {
- NET_VERBOSE_PRINT( "NET: Denying party connection from %s because we were already in a game\n", peerAddress.ToString() );
- SendGoodbye( peerAddress );
- return -1;
- }
- }
- if ( !CheckVersion( msg, peerAddress ) ) {
- idLib::Printf( "NET: Denying user %s with wrong version number\n", peerAddress.ToString() );
- SendGoodbye( peerAddress );
- return -1;
- }
- idPacketProcessor::sessionId_t sessionID = msg.ReadUShort();
- // Check to see if this is a peer trying to connect with a different sessionID
- // If the peer got abruptly disconnected, the peer could be trying to reconnect from a non clean disconnect
- if ( peerNum >= 0 ) {
- peer_t & existingPeer = peers[peerNum];
- assert( existingPeer.GetConnectionState() != CONNECTION_FREE );
- if ( existingPeer.sessionID == sessionID ) {
- return peerNum; // If this is the same sessionID, then assume redundant connection attempt
- }
-
- //
- // This peer must be trying to reconnect from a previous abrupt disconnect
- //
- NET_VERBOSE_PRINT( "NET: Reconnecting peer %s for session %s\n", peerAddress.ToString(), GetLobbyName() );
- // Assume a peer is trying to reconnect from a non clean disconnect
- // We want to set the connection back to FREE manually, so we don't send a goodbye
- existingPeer.connectionState = CONNECTION_FREE;
-
- if ( existingPeer.packetProc != NULL ) {
- delete existingPeer.packetProc;
- existingPeer.packetProc = NULL;
- }
-
- if ( existingPeer.snapProc != NULL ) {
- assert( lobbyType == TYPE_GAME ); // Only games sessions should be creating snap processors
- delete existingPeer.snapProc;
- existingPeer.snapProc = NULL;
- }
- RemoveUsersWithDisconnectedPeers();
- peerNum = -1;
- }
- // See if this was from an invite we sent out. If it wasn't, make sure we aren't invite only
- const bool fromInvite = msg.ReadBool();
- if ( !fromInvite && MatchTypeInviteOnly( parms.matchFlags ) ) {
- idLib::Printf( "NET: Denying user %s because they were not invited to an invite only match\n", peerAddress.ToString() );
- SendGoodbye( peerAddress );
- return -1;
- }
- // Make sure we have room for the users connecting
- if ( !VerifyNumConnectingUsers( msg ) ) {
- NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to being out of user slots\n", peerAddress.ToString(), GetLobbyName() );
- SendGoodbye( peerAddress, true );
- return -1;
- }
- // Make sure there are no lobby id conflicts
- if ( !verify( VerifyLobbyUserIDs( msg ) ) ) {
- NET_VERBOSE_PRINT( "NET: Denying connection from %s in session %s due to lobby id conflict\n", peerAddress.ToString(), GetLobbyName() );
- SendGoodbye( peerAddress, true );
- return -1;
- }
- // Calling AddPeer will set our connectionState to this peer as CONNECTION_CONNECTING (which will get set to CONNECTION_ESTABLISHED below)
- peerNum = AddPeer( peerAddress, sessionID );
- peer_t & newPeer = peers[peerNum];
- assert( newPeer.GetConnectionState() == CONNECTION_CONNECTING );
- assert( lobbyType != GetActingGameStateLobbyType() || newPeer.snapProc != NULL );
-
- // First, add users from this new peer to our user list
- // (which will then forward the list to all peers except peerNum)
- AddUsersFromMsg( msg, peerNum );
- // Mark the peer as connected for this session type
- SetPeerConnectionState( peerNum, CONNECTION_ESTABLISHED );
-
- // Update their heart beat to current
- newPeer.lastHeartBeat = Sys_Milliseconds();
- byte buffer[ idPacketProcessor::MAX_PACKET_SIZE ];
- idBitMsg outmsg( buffer, sizeof( buffer ) );
- // Let them know their peer index on this host
- // peerIndexOnHost (put this here so it shows up in search results when finding out where it's used/referenced)
- outmsg.WriteLong( peerNum );
- // If they are connecting to our party lobby, let them know the party token
- if ( lobbyType == TYPE_PARTY ) {
- outmsg.WriteLong( GetPartyTokenAsHost() );
- }
- if ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) {
- // If this is a game session, reset the loading and ingame flags
- newPeer.loaded = false;
- newPeer.inGame = false;
- }
-
- // Write out current match parms
- parms.Write( outmsg );
- // Send list of existing users to this new peer
- // (the users from the new peer will also be in this list, since we already called AddUsersFromMsg)
- outmsg.WriteByte( GetNumLobbyUsers() );
- for ( int u = 0; u < GetNumLobbyUsers(); u++ ) {
- GetLobbyUser( u )->WriteToMsg( outmsg );
- }
- lobbyBackend->FillMsgWithPostConnectInfo( outmsg );
- NET_VERBOSE_PRINT( "NET: Sending response to %s, lobbyType %s, sessionID %i\n", peerAddress.ToString(), GetLobbyName(), sessionID );
- QueueReliableMessage( peerNum, RELIABLE_HELLO, outmsg.GetReadData(), outmsg.GetSize() );
- if ( MatchTypeIsJoinInProgress( parms.matchFlags ) ) {
- // If have an active game lobby, and someone joins our party, tell them to join our game
- if ( lobbyType == TYPE_PARTY && sessionCB->GetState() >= idSession::GAME_LOBBY ) {
- SendPeerMembersToLobby( peerNum, TYPE_GAME, false );
- }
- // We are are ingame, then start the client loading immediately
- if ( ( lobbyType == TYPE_GAME || lobbyType == TYPE_GAME_STATE ) && sessionCB->GetState() >= idSession::LOADING ) {
- idLib::Printf( "******* JOIN IN PROGRESS ********\n" );
- if ( sessionCB->GetState() == idSession::INGAME ) {
- newPeer.pauseSnapshots = true; // Since this player joined in progress, let game dictate when to start sending snaps
- }
- QueueReliableMessage( peerNum, idLobby::RELIABLE_START_LOADING );
- }
- } else {
- // If we are in a game lobby, and someone joins our party, tell them to join our game
- if ( lobbyType == TYPE_PARTY && sessionCB->GetState() == idSession::GAME_LOBBY ) {
- SendPeerMembersToLobby( peerNum, TYPE_GAME, false );
- }
- }
- // Send mic status of the current lobby to applicable peers
- SendPeersMicStatusToNewUsers( peerNum );
- // If we made is this far, update the users migration game data index
- for ( int u = 0; u < GetNumLobbyUsers(); u++ ) {
- if ( GetLobbyUser( u )->peerIndex == peerNum ) {
- GetLobbyUser( u )->migrationGameData = migrationGameData;
- }
- }
- return peerNum;
- }
- /*
- ========================
- idLobby::InitStateLobbyHost
- ========================
- */
- void idLobby::InitStateLobbyHost() {
- assert( lobbyBackend != NULL );
- // We will be the host
- isHost = true;
- if ( net_headlessServer.GetBool() ) {
- return; // Don't add any players to headless server
- }
- if ( migrationInfo.state != MIGRATE_NONE ) {
- migrationInfo.persistUntilGameEndsData.wasMigratedHost = true; // InitSessionUsersFromLocalUsers needs to know this
- migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame = false;
- // migrationDlg = GDM_MIGRATING_WAITING;
- }
- // Initialize the initial user list for this lobby
- InitSessionUsersFromLocalUsers( MatchTypeIsOnline( parms.matchFlags ) );
- // Set the session's hostAddress to the local players' address.
- const int myUserIndex = GetLobbyUserIndexByLocalUserHandle( sessionCB->GetSignInManager().GetMasterLocalUserHandle() );
- if ( myUserIndex != -1 ) {
- hostAddress = GetLobbyUser( myUserIndex )->address;
- }
- // Since we are the host, we have to register our initial session users with the lobby
- // All additional users will join through AddUsersFromMsg, and RegisterUser is handled in there from here on out.
- // Peers will add users exclusively through AddUsersFromMsg.
- for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
- lobbyUser_t * user = GetLobbyUser( i );
- RegisterUser( user );
- if ( lobbyType == TYPE_PARTY ) {
- user->partyToken = GetPartyTokenAsHost();
- }
- }
- // Set the lobbies skill level
- lobbyBackend->UpdateLobbySkill( GetAverageSessionLevel() );
- // Make sure and register all the addresses of the invites we'll send out as the new host
- if ( migrationInfo.state != MIGRATE_NONE ) {
- // Tell the session that we became the host, so the session mgr can adjust state if needed
- sessionCB->BecameHost( *this );
- // Register this address with this lobbyBackend
- for ( int i = 0; i < migrationInfo.invites.Num(); i++ ) {
- lobbyBackend->RegisterAddress( migrationInfo.invites[i].address );
- }
- }
- }
- /*
- ========================
- idLobby::SendMembersToLobby
- ========================
- */
- void idLobby::SendMembersToLobby( lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ) {
- // It's not our job to send party members to a game if we aren't the party host
- if ( !IsHost() ) {
- return;
- }
- // Send the message to all connected peers
- for ( int i = 0; i < peers.Num(); i++ ) {
- if ( peers[ i ].IsConnected() ) {
- SendPeerMembersToLobby( i, destLobbyType, connectInfo, waitForOtherMembers );
- }
- }
- }
- /*
- ========================
- idLobby::SendMembersToLobby
- ========================
- */
- void idLobby::SendMembersToLobby( idLobby & destLobby, bool waitForOtherMembers ) {
- if ( destLobby.lobbyBackend == NULL ) {
- return; // We don't have a game lobbyBackend to get an address for
- }
- lobbyConnectInfo_t connectInfo = destLobby.lobbyBackend->GetConnectInfo();
- SendMembersToLobby( destLobby.lobbyType, connectInfo, waitForOtherMembers );
- }
- /*
- ========================
- idLobby::SendPeerMembersToLobby
- Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server.
- ========================
- */
- void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, const lobbyConnectInfo_t & connectInfo, bool waitForOtherMembers ) {
- // It's not our job to send party members to a game if we aren't the party host
- if ( !IsHost() ) {
- return;
- }
- assert( peerIndex >= 0 );
- assert( peerIndex < peers.Num() );
- peer_t & peer = peers[ peerIndex ];
- NET_VERBOSE_PRINT( "NET: Sending peer %i (%s) to game lobby\n", peerIndex, peer.address.ToString() );
- if ( !peer.IsConnected() ) {
- idLib::Warning( "NET: Can't send peer %i to game lobby: peer isn't in party", peerIndex );
- return;
- }
- byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
- idBitMsg outmsg( buffer, sizeof( buffer ) );
- // Have lobby fill out msg with connection info
- connectInfo.WriteToMsg( outmsg );
- outmsg.WriteByte( destLobbyType );
- outmsg.WriteBool( waitForOtherMembers );
- QueueReliableMessage( peerIndex, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY, outmsg.GetReadData(), outmsg.GetSize() );
- }
- /*
- ========================
- idLobby::SendPeerMembersToLobby
- Give the address of a game lobby to a particular peer, notifying that peer to send a hello to the same server.
- ========================
- */
- void idLobby::SendPeerMembersToLobby( int peerIndex, lobbyType_t destLobbyType, bool waitForOtherMembers ) {
- idLobby * lobby = sessionCB->GetLobbyFromType( destLobbyType );
- if ( !verify( lobby != NULL ) ) {
- return;
- }
- if ( !verify( lobby->lobbyBackend != NULL ) ) {
- return;
- }
- lobbyConnectInfo_t connectInfo = lobby->lobbyBackend->GetConnectInfo();
- SendPeerMembersToLobby( peerIndex, destLobbyType, connectInfo, waitForOtherMembers );
- }
- /*
- ========================
- idLobby::NotifyPartyOfLeavingGameLobby
- ========================
- */
- void idLobby::NotifyPartyOfLeavingGameLobby() {
- if ( lobbyType != TYPE_PARTY ) {
- return; // We are not a party lobby
- }
- if ( !IsHost() ) {
- return; // We are not the host of a party lobby, we can't do this
- }
- if ( !( sessionCB->GetSessionOptions() & idSession::OPTION_LEAVE_WITH_PARTY ) ) {
- return; // Options aren't set to notify party of leaving
- }
- // Tell our party to leave the game they are in
- for ( int i = 0; i < peers.Num(); i++ ) {
- if ( peers[ i ].IsConnected() ) {
- QueueReliableMessage( i, RELIABLE_PARTY_LEAVE_GAME_LOBBY );
- }
- }
- }
- /*
- ========================
- idLobby::GetPartyTokenAsHost
- ========================
- */
- uint32 idLobby::GetPartyTokenAsHost() {
- assert( lobbyType == TYPE_PARTY );
- assert( IsHost() );
- if ( partyToken == 0 ) {
- // I don't know if this is mathematically sound, but it seems reasonable.
- // Don't do this at app startup (i.e. in the constructor) or it will be a lot less random.
- unsigned long seed = Sys_Milliseconds(); // time app has been running
- idLocalUser * masterUser = session->GetSignInManager().GetMasterLocalUser();
- if ( masterUser != NULL ) {
- seed += idStr::Hash( masterUser->GetGamerTag() );
- }
- partyToken = idRandom( seed ).RandomInt();
- idLib::Printf( "NET: PartyToken is %u (seed = %u)\n", partyToken, seed );
- }
- return partyToken;
- }
- /*
- ========================
- idLobby::EncodeSessionID
- ========================
- */
- idPacketProcessor::sessionId_t idLobby::EncodeSessionID( uint32 key ) const {
- assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) );
- const int numBits = sizeof( idPacketProcessor::sessionId_t ) * 8 - idPacketProcessor::NUM_LOBBY_TYPE_BITS;
- const uint32 mask = ( 1 << numBits ) - 1;
- idPacketProcessor::sessionId_t sessionID = ( key & mask ) << idPacketProcessor::NUM_LOBBY_TYPE_BITS;
- sessionID |= ( lobbyType + 1 );
- return sessionID;
- }
- /*
- ========================
- idLobby::EncodeSessionID
- ========================
- */
- void idLobby::DecodeSessionID( idPacketProcessor::sessionId_t sessionID, uint32 & key ) const {
- assert( sizeof( uint32 ) >= sizeof( idPacketProcessor::sessionId_t ) );
- key = sessionID >> idPacketProcessor::NUM_LOBBY_TYPE_BITS;
- }
- /*
- ========================
- idLobby::GenerateSessionID
- ========================
- */
- idPacketProcessor::sessionId_t idLobby::GenerateSessionID() const {
- idPacketProcessor::sessionId_t sessionID = EncodeSessionID( Sys_Milliseconds() );
-
- // Make sure we can use it
- while ( !SessionIDCanBeUsedForInBand( sessionID ) ) {
- sessionID = IncrementSessionID( sessionID );
- }
-
- return sessionID;
- }
- /*
- ========================
- idLobby::SessionIDCanBeUsedForInBand
- ========================
- */
- bool idLobby::SessionIDCanBeUsedForInBand( idPacketProcessor::sessionId_t sessionID ) const {
- if ( sessionID == idPacketProcessor::SESSION_ID_INVALID ) {
- return false;
- }
- if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_PARTY ) {
- return false;
- }
-
- if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME ) {
- return false;
- }
- if ( sessionID == idPacketProcessor::SESSION_ID_CONNECTIONLESS_GAME_STATE ) {
- return false;
- }
- return true;
- }
- /*
- ========================
- idLobby::IncrementSessionID
- ========================
- */
- idPacketProcessor::sessionId_t idLobby::IncrementSessionID( idPacketProcessor::sessionId_t sessionID ) const {
- // Increment, taking into account valid id's
- while ( 1 ) {
- uint32 key = 0;
-
- DecodeSessionID( sessionID, key );
-
- key++;
-
- sessionID = EncodeSessionID( key );
-
- if ( SessionIDCanBeUsedForInBand( sessionID ) ) {
- break;
- }
- }
-
- return sessionID;
- }
- #define VERIFY_CONNECTED_PEER( p, sessionType_, msgType ) \
- if ( !verify( lobbyType == sessionType_ ) ) { \
- idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
- return; \
- } \
- if ( peers[p].GetConnectionState() != CONNECTION_ESTABLISHED ) { \
- idLib::Printf( "NET: " #msgType ", peer:%s not connected for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
- return; \
- }
- #define VERIFY_CONNECTING_PEER( p, sessionType_, msgType ) \
- if ( !verify( lobbyType == sessionType_ ) ) { \
- idLib::Printf( "NET: " #msgType ", peer:%s invalid session type for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
- return; \
- } \
- if ( peers[p].GetConnectionState() != CONNECTION_CONNECTING ) { \
- idLib::Printf( "NET: " #msgType ", peer:%s not connecting for " #sessionType_ " %i.\n", peer.address.ToString(), sessionType_ ); \
- return; \
- }
- #define VERIFY_FROM_HOST( p, sessionType_, msgType ) \
- VERIFY_CONNECTED_PEER( p, sessionType_, msgType ); \
- if ( p != host ) { \
- idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() ); \
- return; \
- } \
- #define VERIFY_FROM_CONNECTING_HOST( p, sessionType_, msgType ) \
- VERIFY_CONNECTING_PEER( p, sessionType_, msgType ); \
- if ( p != host ) { \
- idLib::Printf( "NET: "#msgType", not from "#sessionType_" host: %s\n", peer.address.ToString() ); \
- return; \
- } \
- /*
- ========================
- idLobby::HandleHelloAck
- ========================
- */
- void idLobby::HandleHelloAck( int p, idBitMsg & msg ) {
- peer_t & peer = peers[p];
- if ( state != STATE_CONNECT_HELLO_WAIT ) {
- idLib::Printf( "NET: Hello ack for session type %s while not waiting for hello.\n", GetLobbyName() );
- SendGoodbye( peer.address ); // We send a customary goodbye to make sure we are not in their list anymore
- return;
- }
- if ( p != host ) {
- // This shouldn't be possible
- idLib::Printf( "NET: Hello ack for session type %s, not from correct host.\n", GetLobbyName() );
- SendGoodbye( peer.address ); // We send a customary goodbye to make sure we are not in their list anymore
- return;
- }
- assert( GetNumLobbyUsers() == 0 );
- NET_VERBOSE_PRINT( "NET: Hello ack for session type %s from %s\n", GetLobbyName(), peer.address.ToString() );
- // We are now connected to this session type
- SetPeerConnectionState( p, CONNECTION_ESTABLISHED );
- // Obtain what our peer index is on the host is
- peerIndexOnHost = msg.ReadLong();
- // If we connected to a party lobby, get the party token from the lobby owner
- if ( lobbyType == TYPE_PARTY ) {
- partyToken = msg.ReadLong();
- }
- // Read match parms
- parms.Read( msg );
- // Update lobbyBackend with parms
- if ( lobbyBackend != NULL ) {
- lobbyBackend->UpdateMatchParms( parms );
- }
- // Populate the user list with the one from the host (which will also include our local users)
- // This ensures the user lists are kept in sync
- FreeAllUsers();
- AddUsersFromMsg( msg, p );
- // Make sure the host has a current heartbeat
- peer.lastHeartBeat = Sys_Milliseconds();
- lobbyBackend->PostConnectFromMsg( msg );
- // Tell the lobby controller to finalize the connection
- SetState( STATE_FINALIZE_CONNECT );
- //
- // Success - We've received an ack from the server, letting us know we've been registered with the lobbies
- //
- }
- /*
- ========================
- idLobby::GetLobbyUserName
- ========================
- */
- const char * idLobby::GetLobbyUserName( lobbyUserID_t lobbyUserID ) const {
- const int index = GetLobbyUserIndexByID( lobbyUserID );
- const lobbyUser_t * user = GetLobbyUser( index );
- if ( user == NULL ) {
- for ( int i = 0; i < disconnectedUsers.Num(); i++ ) {
- if ( disconnectedUsers[i].lobbyUserID.CompareIgnoreLobbyType( lobbyUserID ) ) {
- return disconnectedUsers[i].gamertag;
- }
- }
- return INVALID_LOBBY_USER_NAME;
- }
- return user->gamertag;
- }
- /*
- ========================
- idLobby::GetLobbyUserSkinIndex
- ========================
- */
- int idLobby::GetLobbyUserSkinIndex( lobbyUserID_t lobbyUserID ) const {
- const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
- const lobbyUser_t * user = GetLobbyUser( userIndex );
- return user ? user->selectedSkin : 0;
- }
- /*
- ========================
- idLobby::GetLobbyUserWeaponAutoSwitch
- ========================
- */
- bool idLobby::GetLobbyUserWeaponAutoSwitch( lobbyUserID_t lobbyUserID ) const {
- const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
- const lobbyUser_t * user = GetLobbyUser( userIndex );
- return user ? user->weaponAutoSwitch : true;
- }
- /*
- ========================
- idLobby::GetLobbyUserWeaponAutoReload
- ========================
- */
- bool idLobby::GetLobbyUserWeaponAutoReload( lobbyUserID_t lobbyUserID ) const {
- const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
- const lobbyUser_t * user = GetLobbyUser( userIndex );
- return user ? user->weaponAutoReload: true;
- }
- /*
- ========================
- idLobby::GetLobbyUserLevel
- ========================
- */
- int idLobby::GetLobbyUserLevel( lobbyUserID_t lobbyUserID ) const {
- const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
- const lobbyUser_t * user = GetLobbyUser( userIndex );
- return user ? user->level : 0;
- }
- /*
- ========================
- idLobby::GetLobbyUserQoS
- ========================
- */
- int idLobby::GetLobbyUserQoS( lobbyUserID_t lobbyUserID ) const {
- const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
- if ( IsHost() && IsSessionUserIndexLocal( userIndex ) ) {
- return 0; // Local users on the host of the active session have 0 ping
- }
- const lobbyUser_t * user = GetLobbyUser( userIndex );
- if ( !verify( user != NULL ) ) {
- return 0;
- }
- return user->pingMs;
- }
- /*
- ========================
- idLobby::GetLobbyUserTeam
- ========================
- */
- int idLobby::GetLobbyUserTeam( lobbyUserID_t lobbyUserID ) const {
- const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
- const lobbyUser_t * user = GetLobbyUser( userIndex );
- return user ? user->teamNumber : 0;
- }
- /*
- ========================
- idLobby::SetLobbyUserTeam
- ========================
- */
- bool idLobby::SetLobbyUserTeam( lobbyUserID_t lobbyUserID, int teamNumber ) {
- const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
- lobbyUser_t * user = GetLobbyUser( userIndex );
- if ( user != NULL ) {
- if ( teamNumber != user->teamNumber ) {
- user->teamNumber = teamNumber;
- if ( IsHost() ) {
- byte buffer[ idPacketProcessor::MAX_PACKET_SIZE - 2 ];
- idBitMsg msg( buffer, sizeof( buffer ) );
- CreateUserUpdateMessage( userIndex, msg );
- idBitMsg readMsg;
- readMsg.InitRead( buffer, msg.GetSize() );
- UpdateSessionUserOnPeers( readMsg );
- }
- return true;
- }
- }
- return false;
- }
- /*
- ========================
- idLobby::GetLobbyUserPartyToken
- ========================
- */
- int idLobby::GetLobbyUserPartyToken( lobbyUserID_t lobbyUserID ) const {
- const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
- const lobbyUser_t * user = GetLobbyUser( userIndex );
- return user ? user->partyToken : 0;
- }
- /*
- ========================
- idLobby::GetProfileFromLobbyUser
- ========================
- */
- idPlayerProfile * idLobby::GetProfileFromLobbyUser( lobbyUserID_t lobbyUserID ) {
- const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
- idPlayerProfile * profile = NULL;
- idLocalUser * localUser = GetLocalUserFromLobbyUserIndex( userIndex );
-
- if ( localUser != NULL ) {
- profile = localUser->GetProfile();
- }
-
- if ( profile == NULL ) {
- // Whoops
- profile = session->GetSignInManager().GetDefaultProfile();
- //idLib::Warning( "Returning fake profile until the code is fixed to handle NULL profiles." );
- }
-
- return profile;
- }
- /*
- ========================
- idLobby::GetLocalUserFromLobbyUser
- ========================
- */
- idLocalUser * idLobby::GetLocalUserFromLobbyUser( lobbyUserID_t lobbyUserID ) {
- const int userIndex = GetLobbyUserIndexByID( lobbyUserID );
- return GetLocalUserFromLobbyUserIndex( userIndex );
- }
- /*
- ========================
- idLobby::GetNumLobbyUsersOnTeam
- ========================
- */
- int idLobby::GetNumLobbyUsersOnTeam( int teamNumber ) const {
- int numTeam = 0;
- for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
- if ( GetLobbyUser( i )->teamNumber == teamNumber ) {
- ++numTeam;
- }
- }
- return numTeam;
- }
- /*
- ========================
- idLobby::GetPeerName
- ========================
- */
- const char * idLobby::GetPeerName( int peerNum ) const {
- for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
- if ( !verify( GetLobbyUser( i ) != NULL ) ) {
- continue;
- }
- if ( GetLobbyUser( i )->peerIndex == peerNum ) {
- return GetLobbyUserName( GetLobbyUser( i )->lobbyUserID );
- }
- }
- return INVALID_LOBBY_USER_NAME;
- }
- /*
- ========================
- idLobby::HandleReliableMsg
- ========================
- */
- void idLobby::HandleReliableMsg( int p, idBitMsg & msg ) {
- peer_t & peer = peers[p];
- int reliableType = msg.ReadByte();
-
- //idLib::Printf(" Received reliable msg: %i \n", reliableType );
- const lobbyType_t actingGameStateLobbyType = GetActingGameStateLobbyType();
- if ( reliableType == RELIABLE_HELLO ) {
- VERIFY_FROM_CONNECTING_HOST( p, lobbyType, RELIABLE_HELLO );
- // This is sent from the host acking a request to join the game lobby
- HandleHelloAck( p, msg );
- return;
- } else if ( reliableType == RELIABLE_USER_CONNECT_REQUEST ) {
- VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_USER_CONNECT_REQUEST );
- // This message is sent from a peer requesting for a new user to join the game lobby
- // This will be sent while we are in a game lobby as a host. otherwise, denied.
- NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECT_REQUEST (%s) from %s\n", GetLobbyName(), peer.address.ToString() );
-
- idSession::sessionState_t expectedState = ( lobbyType == TYPE_PARTY ) ? idSession::PARTY_LOBBY : idSession::GAME_LOBBY;
- if ( sessionCB->GetState() == expectedState && IsHost() && NumFreeSlots() > 0 ) { // This assumes only one user in the msg
- // Add user to session, which will also forward the operation to all other peers
- AddUsersFromMsg( msg, p );
- } else {
- // Let peer know user couldn't be added
- HandleUserConnectFailure( p, msg, RELIABLE_USER_CONNECT_DENIED );
- }
- } else if ( reliableType == RELIABLE_USER_CONNECT_DENIED ) {
- // This message is sent back from the host when a RELIABLE_PARTY_USER_CONNECT_REQUEST failed
- VERIFY_FROM_HOST( p, lobbyType, RELIABLE_PARTY_USER_CONNECT_DENIED );
-
- // Remove this user from the sign-in manager, so we don't keep trying to add them
- if ( !sessionCB->GetSignInManager().RemoveLocalUserByHandle( localUserHandle_t( msg.ReadLong() ) ) ) {
- NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_USER_CONNECT_DENIED, local user not found\n" );
- return;
- }
- } else if ( reliableType == RELIABLE_KICK_PLAYER ) {
- VERIFY_FROM_HOST( p, lobbyType, RELIABLE_KICK_PLAYER );
- common->Dialog().AddDialog( GDM_KICKED, DIALOG_ACCEPT, NULL, NULL, false );
- if ( sessionCB->GetPartyLobby().IsHost() ) {
- session->SetSessionOption( idSession::OPTION_LEAVE_WITH_PARTY );
- }
- session->Cancel();
- } else if ( reliableType == RELIABLE_HEADSET_STATE ) {
- HandleHeadsetStateChange( p, msg );
- } else if ( reliableType == RELIABLE_USER_CONNECTED ) {
- // This message is sent back from the host when users have connected, and we need to update our lists to reflect that
- VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_CONNECTED );
- NET_VERBOSE_PRINT( "NET: RELIABLE_USER_CONNECTED (%s) from %s\n", GetLobbyName(), peer.address.ToString() );
- AddUsersFromMsg( msg, p );
- } else if ( reliableType == RELIABLE_USER_DISCONNECTED ) {
- // This message is sent back from the host when users have diconnected, and we need to update our lists to reflect that
- VERIFY_FROM_HOST( p, lobbyType, RELIABLE_USER_DISCONNECTED );
- ProcessUserDisconnectMsg( msg );
- } else if ( reliableType == RELIABLE_MATCH_PARMS ) {
- parms.Read( msg );
- // Update lobby with parms
- if ( lobbyBackend != NULL ) {
- lobbyBackend->UpdateMatchParms( parms );
- }
- } else if ( reliableType == RELIABLE_START_LOADING ) {
- // This message is sent from the host to start loading a map
- VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_START_LOADING );
- NET_VERBOSE_PRINT( "NET: RELIABLE_START_LOADING from %s\n", peer.address.ToString() );
- startLoadingFromHost = true;
- } else if ( reliableType == RELIABLE_LOADING_DONE ) {
- // This message is sent from the peers to state they are done loading the map
- VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_LOADING_DONE );
- unsigned long networkChecksum = 0;
- networkChecksum = msg.ReadLong();
- peer.networkChecksum = networkChecksum;
- peer.loaded = true;
- } else if ( reliableType == RELIABLE_IN_GAME ) {
- VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_IN_GAME );
- peer.inGame = true;
- } else if ( reliableType == RELIABLE_SNAPSHOT_ACK ) {
- VERIFY_CONNECTED_PEER( p, actingGameStateLobbyType, RELIABLE_SNAPSHOT_ACK );
- // update our base state for his last received snapshot
- int snapNum = msg.ReadLong();
- float receivedBps = msg.ReadQuantizedUFloat< BANDWIDTH_REPORTING_MAX, BANDWIDTH_REPORTING_BITS >();
- // Update reported received bps
- if ( peer.receivedBpsIndex != snapNum ) {
- // Only do this the first time we get reported bps per snapshot. Subsequent ACKs of the same shot will usually have lower reported bps
- // due to more time elapsing but not receiving a new ss
- peer.receivedBps = receivedBps;
- peer.receivedBpsIndex = snapNum;
- }
- ApplySnapshotDelta( p, snapNum );
-
- //idLib::Printf( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum );
- NET_VERBOSESNAPSHOT_PRINT_LEVEL( 2, va( "NET: Peer %d Ack'd snapshot %d\n", p, snapNum ) );
- } else if ( reliableType == RELIABLE_RESOURCE_ACK ) {
- } else if ( reliableType == RELIABLE_UPDATE_MATCH_PARMS ) {
- VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_UPDATE_MATCH_PARMS );
- int msgType = msg.ReadLong();
- sessionCB->HandlePeerMatchParamUpdate( p, msgType );
- } else if ( reliableType == RELIABLE_MATCHFINISHED ) {
- VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_MATCHFINISHED );
- sessionCB->ClearMigrationState();
- } else if ( reliableType == RELIABLE_ENDMATCH ) {
- VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH );
- sessionCB->EndMatchInternal();
- } else if ( reliableType == RELIABLE_ENDMATCH_PREMATURE ) {
- VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_ENDMATCH_PREMATURE );
- sessionCB->EndMatchInternal( true );
- } else if ( reliableType == RELIABLE_START_MATCH_GAME_LOBBY_HOST ) {
- // This message should be from the host of the game lobby, telling us (as the host of the GameStateLobby) to start loading
- VERIFY_CONNECTED_PEER( p, TYPE_GAME_STATE, RELIABLE_START_MATCH_GAME_LOBBY_HOST );
- if ( session->GetState() >= idSession::LOADING ) {
- NET_VERBOSE_PRINT( "NET: RELIABLE_START_MATCH_GAME_LOBBY_HOST already loading\n" );
- return;
- }
- // Read match parms, and start loading
- parms.Read( msg );
- // Send these new match parms to currently connected peers
- SendMatchParmsToPeers();
- startLoadingFromHost = true; // Hijack this flag
- } else if ( reliableType == RELIABLE_ARBITRATE ) {
- VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE );
- // Host telling us to arbitrate
- // Set a flag to do this later, since the lobby may not be in a state where it can fulfil the request at the moment
- respondToArbitrate = true;
- } else if ( reliableType == RELIABLE_ARBITRATE_OK ) {
- VERIFY_CONNECTED_PEER( p, TYPE_GAME, RELIABLE_ARBITRATE_OK );
-
- NET_VERBOSE_PRINT( "NET: Got an arbitration ok from %d\n", p );
- everyoneArbitrated = true;
- for ( int i = 0; i < GetNumLobbyUsers(); i++ ) {
- lobbyUser_t * user = GetLobbyUser( i );
- if ( !verify( user != NULL ) ) {
- continue;
- }
- if ( user->peerIndex == p ) {
- user->arbitrationAcked = true;
- } else if ( !user->arbitrationAcked ) {
- everyoneArbitrated = false;
- }
- }
- if ( everyoneArbitrated ) {
- NET_VERBOSE_PRINT( "NET: Everyone says they registered for arbitration, verifying\n" );
- lobbyBackend->Arbitrate();
- //sessionCB->EveryoneArbitrated();
- return;
- }
- } else if ( reliableType == RELIABLE_POST_STATS ) {
- VERIFY_FROM_HOST( p, actingGameStateLobbyType, RELIABLE_POST_STATS );
- sessionCB->RecvLeaderboardStats( msg );
- } else if ( reliableType == RELIABLE_SESSION_USER_MODIFIED ) {
- VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_SESSION_USER_MODIFIED );
- UpdateSessionUserOnPeers( msg );
- } else if ( reliableType == RELIABLE_UPDATE_SESSION_USER ) {
- VERIFY_FROM_HOST( p, lobbyType, RELIABLE_UPDATE_SESSION_USER );
- HandleUpdateSessionUser( msg );
- } else if ( reliableType == RELIABLE_CONNECT_AND_MOVE_TO_LOBBY ) {
- VERIFY_FROM_HOST( p, lobbyType, RELIABLE_CONNECT_AND_MOVE_TO_LOBBY );
-
- NET_VERBOSE_PRINT( "NET: RELIABLE_CONNECT_AND_MOVE_TO_LOBBY\n" );
- if ( IsHost() ) {
- idLib::Printf( "RELIABLE_CONNECT_AND_MOVE_TO_LOBBY: We are the host.\n" );
- return;
- }
- // Get connection info
- lobbyConnectInfo_t connectInfo;
- connectInfo.ReadFromMsg( msg );
- const lobbyType_t destLobbyType = (lobbyType_t)msg.ReadByte();
- const bool waitForMembers = msg.ReadBool();
- assert( destLobbyType > lobbyType ); // Make sure this is a proper transition (i.e. TYPE_PARTY moves to TYPE_GAME, TYPE_GAME moves to TYPE_GAME_STATE)
- sessionCB->ConnectAndMoveToLobby( destLobbyType, connectInfo, waitForMembers );
- } else if ( reliableType == RELIABLE_PARTY_CONNECT_OK ) {
- VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_CONNECT_OK );
- if ( !sessionCB->GetGameLobby().waitForPartyOk ) {
- idLib::Printf( "RELIABLE_PARTY_CONNECT_OK: Wasn't waiting for ok.\n" );
- }
- sessionCB->GetGameLobby().waitForPartyOk = false;
- } else if ( reliableType == RELIABLE_PARTY_LEAVE_GAME_LOBBY ) {
- VERIFY_FROM_HOST( p, TYPE_PARTY, RELIABLE_PARTY_LEAVE_GAME_LOBBY );
-
- NET_VERBOSE_PRINT( "NET: RELIABLE_PARTY_LEAVE_GAME_LOBBY\n" );
-
- if ( sessionCB->GetState() != idSession::GAME_LOBBY ) {
- idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Not in a game lobby, ignoring.\n" );
- return;
- }
- if ( IsHost() ) {
- idLib::Printf( "RELIABLE_PARTY_LEAVE_GAME_LOBBY: Host of party, ignoring.\n" );
- return;
- }
- sessionCB->LeaveGameLobby();
- } else if ( IsReliablePlayerToPlayerType( reliableType ) ) {
- HandleReliablePlayerToPlayerMsg( p, msg, reliableType );
- } else if ( reliableType == RELIABLE_PING ) {
- HandleReliablePing( p, msg );
- } else if ( reliableType == RELIABLE_PING_VALUES ) {
- HandlePingValues( msg );
- } else if ( reliableType == RELIABLE_BANDWIDTH_VALUES ) {
- HandleBandwidhTestValue( p, msg );
- } else if ( reliableType == RELIABLE_MIGRATION_GAME_DATA ) {
- HandleMigrationGameData( msg );
- } else if ( reliableType >= RELIABLE_GAME_DATA ) {
- VERIFY_CONNECTED_PEER( p, lobbyType, RELIABLE_GAME_DATA );
- common->NetReceiveReliable( p, reliableType - RELIABLE_GAME_DATA, msg );
- } else if ( reliableType == RELIABLE_DUMMY_MSG ) {
- // Ignore dummy msg's
- NET_VERBOSE_PRINT( "NET: ignoring dummy msg from %s\n", peer.address.ToString() );
- } else {
- NET_VERBOSE_PRINT( "NET: Unknown reliable packet type %d from %s\n", reliableType, peer.address.ToString() );
- }
- }
- /*
- ========================
- idLobby::GetTotalOutgoingRate
- ========================
- */
- int idLobby::GetTotalOutgoingRate() {
- int totalSendRate = 0;
- for ( int p = 0; p < peers.Num(); p++ ) {
- const peer_t & peer = peers[p];
-
- if ( !peer.IsConnected() ) {
- continue;
- }
-
- const idPacketProcessor & proc = *peer.packetProc;
- totalSendRate += proc.GetOutgoingRateBytes();
- }
- return totalSendRate;
- }
- /*
- ========================
- idLobby::DrawDebugNetworkHUD
- ========================
- */
- extern idCVar net_forceUpstream;
- void idLobby::DrawDebugNetworkHUD() const {
- int totalSendRate = 0;
- int totalRecvRate = 0;
- float totalSentMB = 0.0f;
- float totalRecvMB = 0.0f;
-
- const float Y_OFFSET = 20.0f;
- const float X_OFFSET = 20.0f;
- const float Y_SPACING = 15.0f;
- float curY = Y_OFFSET;
- int numLines = ( net_forceUpstream.GetFloat() != 0.0f ? 6: 5 );
- renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 1550, ( peers.Num() + numLines ) * Y_SPACING + 20.0f );
- renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "# Peer | Sent kB/s | Recv kB/s | Sent MB | Recv MB | Ping | L | % | R.NM | R.SZ | R.AK | T", colorGreen, false );
- curY += Y_SPACING;
- renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false );
- curY += Y_SPACING;
- for ( int p = 0; p < peers.Num(); p++ ) {
- const peer_t & peer = peers[p];
-
- if ( !peer.IsConnected() ) {
- continue;
- }
-
- const idPacketProcessor & proc = *peer.packetProc;
-
- totalSendRate += proc.GetOutgoingRateBytes();
- totalRecvRate += proc.GetIncomingRateBytes();
- float sentKps = (float)proc.GetOutgoingRateBytes() / 1024.0f;
- float recvKps = (float)proc.GetIncomingRateBytes() / 1024.0f;
- float sentMB = (float)proc.GetOutgoingBytes() / ( 1024.0f * 1024.0f );
- float recvMB = (float)proc.GetIncomingBytes() / ( 1024.0f * 1024.0f );
-
- totalSentMB += sentMB;
- totalRecvMB += recvMB;
-
- idVec4 color = sentKps > 20.0f ? colorRed : colorGreen;
- int resourcePercent = 0;
- idStr name = peer.address.ToString();
-
- name += lobbyType == TYPE_PARTY ? "(P": "(G";
- name += host == p ? ":H)" : ":C)";
- renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "%i %22s | %2.02f kB/s | %2.02f kB/s | %2.02f MB | %2.02f MB |%4i ms | %i | %i%% | %i | %i | %i | %2.2f / %2.2f / %i", p, name.c_str(), sentKps, recvKps, sentMB, recvMB, peer.lastPingRtt, peer.loaded, resourcePercent, peer.packetProc->NumQueuedReliables(), peer.packetProc->GetReliableDataSize(), peer.packetProc->NeedToSendReliableAck(), peer.snapHz, peer.maxSnapBps, peer.failedPingRecoveries ), color, false );
- curY += Y_SPACING;
- }
- renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------------------------------------------------------------------------", colorGreen, false );
- curY += Y_SPACING;
- float totalSentKps = (float)totalSendRate / 1024.0f;
- float totalRecvKps = (float)totalRecvRate / 1024.0f;
-
- idVec4 color = totalSentKps > 100.0f ? colorRed : colorGreen;
-
- renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "# %20s | %2.02f KB/s | %2.02f KB/s | %2.02f MB | %2.02f MB", "", totalSentKps, totalRecvKps, totalSentMB, totalRecvMB ), color, false );
- curY += Y_SPACING;
- if ( net_forceUpstream.GetFloat() != 0.0f ) {
- float upstreamDropRate = session->GetUpstreamDropRate();
- float upstreamQueuedRate = session->GetUpstreamQueueRate();
- int queuedBytes = session->GetQueuedBytes();
- renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Queued: %d | Dropping: %2.02f kB/s Queue: %2.02f kB/s -> Effective %2.02f kB/s", queuedBytes, upstreamDropRate / 1024.0f, upstreamQueuedRate / 1024.0f, totalSentKps - ( upstreamDropRate / 1024.0f ) + ( upstreamQueuedRate / 1024.0f ) ), color, false );
- }
- }
- /*
- ========================
- idLobby::DrawDebugNetworkHUD2
- ========================
- */
- void idLobby::DrawDebugNetworkHUD2() const {
- int totalSendRate = 0;
- int totalRecvRate = 0;
- const float Y_OFFSET = 20.0f;
- const float X_OFFSET = 20.0f;
- const float Y_SPACING = 15.0f;
- float curY = Y_OFFSET;
-
- renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 550, (peers.Num() + 4) * Y_SPACING + 20.0f );
- const char* stateName = session->GetStateString();
- renderSystem->DrawFilled( idVec4( 1.0f, 1.0f, 1.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, 550, (peers.Num() + 5) * Y_SPACING + 20.0f );
- renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), va("State: %s. Local time: %d", stateName, Sys_Milliseconds() ), colorGreen, false );
- curY += Y_SPACING;
- renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "Peer | Sent kB/s | Recv kB/s | L | R | Resources", colorGreen, false );
- curY += Y_SPACING;
- renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false );
- curY += Y_SPACING;
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( !peers[ p ].IsConnected() ) {
- continue;
- }
- idPacketProcessor & proc = *peers[ p ].packetProc;
- totalSendRate += proc.GetOutgoingRate2();
- totalRecvRate += proc.GetIncomingRate2();
- float sentKps = ( float )proc.GetOutgoingRate2() / 1024.0f;
- float recvKps = ( float )proc.GetIncomingRate2() / 1024.0f;
- // should probably complement that with a bandwidth reading
- // right now I am mostly concerned about fragmentation and the latency spikes it will cause
- idVec4 color = proc.TickFragmentAccumulator() ? colorRed : colorGreen;
-
- int rLoaded = peers[ p ].numResources;
- int rTotal = 0;
- // show the names of the clients connected to the server. Also make sure it looks reasonably good.
- idStr peerName;
- if ( IsHost() ) {
- peerName = GetPeerName( p );
- int MAX_PEERNAME_LENGTH = 10;
- int nameLength = peerName.Length();
- if ( nameLength > MAX_PEERNAME_LENGTH ) {
- peerName = peerName.Left( MAX_PEERNAME_LENGTH );
- } else if ( nameLength < MAX_PEERNAME_LENGTH ) {
- idStr filler;
- filler.Fill( ' ', MAX_PEERNAME_LENGTH );
- peerName += filler.Left( MAX_PEERNAME_LENGTH - nameLength );
- }
- } else {
- peerName = "Local ";
- }
- renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "%i - %s | %2.02f kB/s | %2.02f kB/s | %i | %i | %d/%d", p, peerName.c_str(), sentKps, recvKps, peers[p].loaded, peers[p].address.UsingRelay(), rLoaded, rTotal ), color, false );
- curY += Y_SPACING;
- }
- renderSystem->DrawSmallStringExt( idMath::Ftoi( X_OFFSET ), idMath::Ftoi( curY ), "------------------------------------------------------------------", colorGreen, false );
- curY += Y_SPACING;
- float totalSentKps = (float)totalSendRate / 1024.0f;
- float totalRecvKps = (float)totalRecvRate / 1024.0f;
- renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Total | %2.02f KB/s | %2.02f KB/s", totalSentKps, totalRecvKps ), colorGreen, false );
- }
- /*
- ========================
- idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics
- ========================
- */
- idCVar net_debughud3_bps_max( "net_debughud3_bps_max", "5120.0f", CVAR_FLOAT, "Highest factor of server base snapRate that a client can be throttled" );
- void idLobby::DrawDebugNetworkHUD_ServerSnapshotMetrics( bool draw ) {
- const float Y_OFFSET = 20.0f;
- const float X_OFFSET = 20.0f;
- const float Y_SPACING = 15.0f;
- idVec4 color = colorWhite;
- float curY = Y_OFFSET;
- if ( !draw ) {
- for ( int p=0; p < peers.Num(); p++ ) {
- for ( int i=0; i < peers[p].debugGraphs.Num(); i++ ) {
- if ( peers[p].debugGraphs[i] != NULL ) {
- peers[p].debugGraphs[i]->Enable( false );
- } else {
- return;
- }
- }
- }
- return;
- }
- static int lastTime = 0;
- int time = Sys_Milliseconds();
- for ( int p = 0; p < peers.Num(); p++ ) {
- peer_t & peer = peers[p];
- if ( !peer.IsConnected() ) {
- continue;
- }
- idPacketProcessor * packetProc = peer.packetProc;
- idSnapshotProcessor * snapProc = peer.snapProc;
- if ( !verify( packetProc != NULL && snapProc != NULL ) ) {
- continue;
- }
- int snapSeq = snapProc->GetSnapSequence();
- int snapBase = snapProc->GetBaseSequence();
- int deltaSeq = snapSeq - snapBase;
- bool throttled = peer.throttledSnapRate > common->GetSnapRate();
- int numLines = net_forceUpstream.GetBool() ? 5 : 4;
- const int width = renderSystem->GetWidth()/2.0f - (X_OFFSET * 2);
- enum netDebugGraphs_t {
- GRAPH_SNAPSENT,
- GRAPH_OUTGOING,
- GRAPH_INCOMINGREPORTED,
- GRAPH_MAX
- };
- peer.debugGraphs.SetNum( GRAPH_MAX, NULL );
- for ( int i=0; i < GRAPH_MAX; i++ ) {
- // Initialize graphs
- if ( peer.debugGraphs[i] == NULL ) {
- peer.debugGraphs[i] = console->CreateGraph( 500 );
- if ( !verify( peer.debugGraphs[i] != NULL ) ) {
- continue;
- }
-
- peer.debugGraphs[i]->SetPosition( X_OFFSET - 10.0f + width, curY - 10.0f, width , Y_SPACING * numLines );
- }
-
- peer.debugGraphs[i]->Enable( true );
- }
- renderSystem->DrawFilled( idVec4( 0.0f, 0.0f, 0.0f, 0.7f ), X_OFFSET - 10.0f, curY - 10.0f, width, ( Y_SPACING * numLines ) + 20.0f );
- renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Peer %d - %s RTT %d %sPeerSnapRate: %d %s", p, GetPeerName( p ), peer.lastPingRtt, throttled ? "^1" : "^2", peer.throttledSnapRate/1000, throttled ? "^1Throttled" : "" ), color, false );
- curY += Y_SPACING;
- renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "SnapSeq %d BaseSeq %d Delta %d Queue %d", snapSeq, snapBase, deltaSeq, snapProc->GetSnapQueueSize() ), color, false );
- curY += Y_SPACING;
- renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Reliables: %d / %d bytes Reliable Ack: %d", packetProc->NumQueuedReliables(), packetProc->GetReliableDataSize(), packetProc->NeedToSendReliableAck() ), color, false );
- curY += Y_SPACING;
- renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Outgoing %.2f kB/s Reported %.2f kB/s Throttle: %.2f", peer.packetProc->GetOutgoingRateBytes() / 1024.0f, peers[p].receivedBps / 1024.0f, peer.receivedThrottle ), color, false );
- curY += Y_SPACING;
- if ( net_forceUpstream.GetFloat() != 0.0f ) {
- float upstreamDropRate = session->GetUpstreamDropRate();
- float upstreamQueuedRate = session->GetUpstreamQueueRate();
- int queuedBytes = session->GetQueuedBytes();
- renderSystem->DrawSmallStringExt( X_OFFSET, curY, va( "Queued: %d | Dropping: %2.02f kB/s Queue: %2.02f kB/s ", queuedBytes, upstreamDropRate / 1024.0f, upstreamQueuedRate / 1024.0f ), color, false );
-
- }
- curY += Y_SPACING;
-
- if ( peer.debugGraphs[GRAPH_SNAPSENT] != NULL ) {
- if ( peer.lastSnapTime > lastTime ) {
- peer.debugGraphs[GRAPH_SNAPSENT]->SetValue(-1, 1.0f, colorBlue );
- } else {
- peer.debugGraphs[GRAPH_SNAPSENT]->SetValue(-1, 0.0f, colorBlue );
- }
- }
- if ( peer.debugGraphs[GRAPH_OUTGOING] != NULL ) {
- idVec4 bgColor( vec4_zero );
- peer.debugGraphs[GRAPH_OUTGOING]->SetBackgroundColor( bgColor );
- idVec4 lineColor = colorLtGrey;
- lineColor.w = 0.5f;
- float outgoingRate = peer.sentBpsHistory[ peer.receivedBpsIndex % MAX_BPS_HISTORY ];
- // peer.packetProc->GetOutgoingRateBytes()
- peer.debugGraphs[GRAPH_OUTGOING]->SetValue(-1, idMath::ClampFloat( 0.0f, 1.0f, outgoingRate / net_debughud3_bps_max.GetFloat() ), lineColor );
- }
- if ( peer.debugGraphs[GRAPH_INCOMINGREPORTED] != NULL ) {
- idVec4 lineColor = colorYellow;
- extern idCVar net_peer_throttle_bps_peer_threshold_pct;
- extern idCVar net_peer_throttle_bps_host_threshold;
-
- if ( peer.packetProc->GetOutgoingRateBytes() > net_peer_throttle_bps_host_threshold.GetFloat() ) {
- float pct = peer.packetProc->GetOutgoingRateBytes() > 0.0f ? peer.receivedBps / peer.packetProc->GetOutgoingRateBytes() : 0.0f;
- if ( pct < net_peer_throttle_bps_peer_threshold_pct.GetFloat() ) {
- lineColor = colorRed;
- } else {
- lineColor = colorGreen;
- }
- }
- idVec4 bgColor( vec4_zero );
- peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetBackgroundColor( bgColor );
- peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetFillMode( idDebugGraph::GRAPH_LINE );
- peer.debugGraphs[GRAPH_INCOMINGREPORTED]->SetValue(-1, idMath::ClampFloat( 0.0f, 1.0f, peer.receivedBps / net_debughud3_bps_max.GetFloat() ), lineColor );
- }
-
- // Skip down
- curY += ( Y_SPACING * 2.0f);
- }
- lastTime = time;
- }
- /*
- ========================
- idLobby::CheckHeartBeats
- ========================
- */
- void idLobby::CheckHeartBeats() {
- // Disconnect peers that haven't responded within net_peerTimeoutInSeconds
- int time = Sys_Milliseconds();
- int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000;
- if ( sessionCB->GetState() < idSession::LOADING && migrationInfo.state == MIGRATE_NONE ) {
- // Use shorter timeout in lobby (TCR)
- timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds_Lobby", net_peerTimeoutInSeconds_Lobby.GetInteger() ) * 1000;
- }
- if ( timeoutInMs > 0 ) {
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( peers[p].IsConnected() ) {
- bool peerTimeout = false;
- if( time - peers[p].lastHeartBeat > timeoutInMs ) {
- peerTimeout = true;
- }
- // if reliable queue is almost full, disconnect the peer.
- // (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never
- // have more than 3 or 4 queued)
- if ( peers[ p ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE - 1 ) {
- peerTimeout = true;
- }
-
- if( peerTimeout ) {
- // Disconnect the peer from any sessions we are a host of
- if ( IsHost() ) {
- idLib::Printf("Peer %i timed out for %s session @ %d (lastHeartBeat %d)\n", p, GetLobbyName(), time, peers[p].lastHeartBeat );
- DisconnectPeerFromSession( p );
- }
- // Handle peers not receiving a heartbeat from the host in awhile
- if ( IsPeer() ) {
- if ( migrationInfo.state != MIGRATE_PICKING_HOST ) {
- idLib::Printf("Host timed out for %s session\n", GetLobbyName() );
- // Pick a host for this session
- PickNewHost();
- }
- }
- }
- }
- }
- }
- if ( IsHost() && lobbyType == GetActingGameStateLobbyType() ) {
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( !peers[p].IsConnected() ) {
- continue;
- }
- CheckPeerThrottle( p );
- }
- }
- }
- /*
- ========================
- idLobby::CheckHeartBeats
- ========================
- */
- bool idLobby::IsLosingConnectionToHost() const {
- if ( !verify( IsPeer() && host >= 0 && host < peers.Num() ) ) {
- return false;
- }
- if ( !peers[ host ].IsConnected() ) {
- return true;
- }
- int time = Sys_Milliseconds();
- int timeoutInMs = session->GetTitleStorageInt( "net_peerTimeoutInSeconds", net_peerTimeoutInSeconds.GetInteger() ) * 1000;
- // return true if heartbeat > half the timeout length
- if ( timeoutInMs > 0 && time - peers[ host ].lastHeartBeat > timeoutInMs / 2 ) {
- return true;
- }
- // return true if reliable queue is more than half full
- // (this seems reasonable since the reliable queue is set to 64 currently. In practice we should never
- // have more than 3 or 4 queued)
- if ( peers[ host ].packetProc->NumQueuedReliables() > idPacketProcessor::MAX_RELIABLE_QUEUE / 2 ) {
- return true;
- }
- return false;
- }
- /*
- ========================
- idLobby::IsMigratedStatsGame
- ========================
- */
- bool idLobby::IsMigratedStatsGame() const {
- if ( !IsLobbyActive() ) {
- return false;
- }
- if ( lobbyType != TYPE_GAME ) {
- return false; // Only game session migrates games stats
- }
- if ( !MatchTypeHasStats( parms.matchFlags ) ) {
- return false; // Only stats games migrate stats
- }
- if ( !MatchTypeIsRanked( parms.matchFlags ) ) {
- return false; // Only ranked games should migrate stats into new game
- }
- return migrationInfo.persistUntilGameEndsData.wasMigratedGame && migrationInfo.persistUntilGameEndsData.hasGameData;
- }
- /*
- ========================
- idLobby::ShouldRelaunchMigrationGame
- returns true if we are hosting a migrated game and we had valid migration data
- ========================
- */
- bool idLobby::ShouldRelaunchMigrationGame() const {
- if ( IsMigrating() ) {
- return false; // Don't relaunch until all clients have reconnected
- }
- if ( !IsMigratedStatsGame() ) {
- return false; // If we are not migrating stats, we don't want to relaunch a new game
- }
- if ( !migrationInfo.persistUntilGameEndsData.wasMigratedHost ) {
- return false; // Only relaunch if we are the host
- }
- if ( migrationInfo.persistUntilGameEndsData.hasRelaunchedMigratedGame ) {
- return false; // We already relaunched this game
- }
- return true;
- }
- /*
- ========================
- idLobby::ShouldShowMigratingDialog
- ========================
- */
- bool idLobby::ShouldShowMigratingDialog() const {
- if ( IsMigrating() ) {
- return true; // If we are in the process of truly migrating, then definitely return true
- }
- if ( sessionCB->GetState() == idSession::INGAME ) {
- return false;
- }
- // We're either waiting on the server (which could be us) to relaunch, so show the dialog
- return IsMigratedStatsGame() && sessionCB->GetState() != idSession::INGAME;
- }
- /*
- ========================
- idLobby::IsMigrating
- ========================
- */
- bool idLobby::IsMigrating() const {
- return migrationInfo.state != idLobby::MIGRATE_NONE;
- }
- /*
- ========================
- idLobby::PingPeers
- Host only.
- ========================
- */
- void idLobby::PingPeers() {
- if ( !verify( IsHost() )) {
- return;
- }
- const int now = Sys_Milliseconds();
- pktPing_t packet;
- memset( &packet, 0, sizeof( packet ) ); // We're gonna memset like it's 1999.
- packet.timestamp = now;
- byte packetCopy[ sizeof( packet ) ];
- idBitMsg msg( packetCopy, sizeof( packetCopy ) );
- msg.WriteLong( packet.timestamp );
- for ( int i = 0; i < peers.Num(); ++i ) {
- peer_t & peer = peers[ i ];
- if ( !peer.IsConnected() ) {
- continue;
- }
- if ( peer.nextPing <= now ) {
- peer.nextPing = now + PING_INTERVAL_MS;
- QueueReliableMessage( i, RELIABLE_PING, msg.GetReadData(), msg.GetSize() );
- }
- }
- }
- /*
- ========================
- idLobby::ThrottlePeerSnapRate
- ========================
- */
- void idLobby::ThrottlePeerSnapRate( int p ) {
- if ( !verify( IsHost() ) || !verify( p >= 0 ) ) {
- return;
- }
- peers[p].throttledSnapRate = common->GetSnapRate() * 2;
- idLib::Printf( "^1Throttling peer %d %s!\n", p, GetPeerName(p) );
- idLib::Printf( " New snaprate: %d\n", peers[p].throttledSnapRate / 1000 );
- }
- /*
- ========================
- idLobby::SaturatePeers
- ========================
- */
- void idLobby::BeginBandwidthTest() {
- if ( !verify( IsHost() ) ) {
- idLib::Warning("Bandwidth test should only be done on host");
- return;
- }
- if ( bandwidthChallengeStartTime > 0 ) {
- idLib::Warning("Already started bandwidth test");
- return;
- }
- int time = Sys_Milliseconds();
- bandwidthChallengeStartTime = time;
- bandwidthChallengeEndTime = 0;
- bandwidthChallengeFinished = false;
- bandwidthChallengeNumGoodSeq = 0;
- for ( int p = 0; p < peers.Num(); ++p ) {
- if ( !peers[ p ].IsConnected() ) {
- continue;
- }
- if ( !verify( peers[ p ].packetProc != NULL ) ) {
- continue;
- }
- peers[ p ].bandwidthSequenceNum = 0;
- peers[ p ].bandwidthChallengeStartSendTime = 0;
- peers[ p ].bandwidthChallengeResults = false;
- peers[ p ].bandwidthChallengeSendComplete = false;
- peers[ p ].bandwidthTestBytes = peers[ p ].packetProc->GetOutgoingBytes(); // cache this off so we can see the difference when we are done
- }
- }
- /*
- ========================
- idLobby::SaturatePeers
- ========================
- */
- bool idLobby::BandwidthTestStarted() {
- return bandwidthChallengeStartTime != 0;
- }
- /*
- ========================
- idLobby::ServerUpdateBandwidthTest
- ========================
- */
- void idLobby::ServerUpdateBandwidthTest() {
- if ( bandwidthChallengeStartTime <= 0 ) {
- // Not doing a test
- return;
- }
-
- if ( !verify( IsHost() )) {
- return;
- }
-
- int time = Sys_Milliseconds();
- if ( bandwidthChallengeFinished ) {
- // test is over
- return;
- }
- idRandom random;
- random.SetSeed( time );
- bool sentAll = true;
- bool recAll = true;
- for ( int i = 0; i < peers.Num(); ++i ) {
- peer_t & peer = peers[ i ];
- if ( !peer.IsConnected() ) {
- continue;
- }
- if ( peer.bandwidthChallengeResults ) {
- continue;
- }
- recAll = false;
- if ( peer.bandwidthChallengeSendComplete ) {
- continue;
- }
- sentAll = false;
- if ( time - peer.bandwidthTestLastSendTime < session->GetTitleStorageInt( "net_bw_test_interval", net_bw_test_interval.GetInteger() ) ) {
- continue;
- }
- if ( peer.packetProc->HasMoreFragments() ) {
- continue;
- }
- if ( peer.bandwidthChallengeStartSendTime == 0 ) {
- peer.bandwidthChallengeStartSendTime = time;
- }
- peer.bandwidthTestLastSendTime = time;
- // Ok, send him a big packet
- byte buffer[ idPacketProcessor::MAX_OOB_MSG_SIZE ]; // <---- NOTE - When calling ProcessOutgoingMsg with true for oob, we can't go over this size
- idBitMsg msg( buffer, sizeof(buffer) );
- msg.WriteLong( peer.bandwidthSequenceNum++ );
- unsigned int randomSize = Min( (unsigned int)(sizeof(buffer) - 12), (unsigned int)session->GetTitleStorageInt( "net_bw_test_packetSizeBytes", net_bw_test_packetSizeBytes.GetInteger() ) );
- msg.WriteLong( randomSize );
-
- for ( unsigned int j=0; j < randomSize; j++ ) {
- msg.WriteByte( random.RandomInt( 255 ) );
- }
-
- unsigned int checksum = MD5_BlockChecksum( &buffer[8], randomSize );
- msg.WriteLong( checksum );
- NET_VERBOSE_PRINT("Net: Sending bw challenge to peer %d time %d packet size %d\n", i, time, msg.GetSize() );
- ProcessOutgoingMsg( i, buffer, msg.GetSize(), true, OOB_BANDWIDTH_TEST );
- if ( session->GetTitleStorageInt( "net_bw_test_numPackets", net_bw_test_numPackets.GetInteger() ) > 0 && peer.bandwidthSequenceNum >= net_bw_test_numPackets.GetInteger() ) {
- int sentBytes = peers[i].packetProc->GetOutgoingBytes() - peers[i].bandwidthTestBytes; // FIXME: this won't include the last sent msg
- peers[i].bandwidthTestBytes = sentBytes; // this now means total bytes sent (we don't care about starting/ending total bytes sent to peer)
- peers[i].bandwidthChallengeSendComplete = true;
- NET_VERBOSE_PRINT("Sent enough packets to peer %d for bandwidth test in %dms. Total bytes: %d\n", i, time - bandwidthChallengeStartTime, sentBytes );
- }
- }
- if ( sentAll ) {
- if ( bandwidthChallengeEndTime == 0 ) {
- // We finished sending all our packets, set the timeout time
- bandwidthChallengeEndTime = time + session->GetTitleStorageInt( "net_bw_test_host_timeout", net_bw_test_host_timeout.GetInteger() );
- NET_VERBOSE_PRINT("Net: finished sending BWC to peers. Waiting until %d to hear back\n", bandwidthChallengeEndTime );
- }
- }
-
- if ( recAll ) {
- bandwidthChallengeFinished = true;
- bandwidthChallengeStartTime = 0;
- } else if ( bandwidthChallengeEndTime != 0 && bandwidthChallengeEndTime < time ) {
- // Timed out waiting for someone - throttle them and move on
- NET_VERBOSE_PRINT("^2Net: timed out waiting for bandwidth challenge results \n");
- for ( int i=0; i < peers.Num(); i++ ) {
- NET_VERBOSE_PRINT(" Peer[%d] %s. SentAll: %d RecAll: %d\n", i, GetPeerName(i), peers[i].bandwidthChallengeSendComplete, peers[i].bandwidthChallengeResults );
- if ( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults ) {
- ThrottlePeerSnapRate( i );
- }
- }
- bandwidthChallengeFinished = true;
- bandwidthChallengeStartTime = 0;
- }
- }
- /*
- ========================
- idLobby::UpdateBandwidthTest
- This will be called on clients to check current state of bandwidth testing
- ========================
- */
- void idLobby::ClientUpdateBandwidthTest() {
- if ( !verify( !IsHost() ) || !verify( host >= 0 ) ) {
- return;
- }
- if ( !peers[host].IsConnected() ) {
- return;
- }
- if ( bandwidthChallengeStartTime <= 0 ) {
- // Not doing a test
- return;
- }
- int time = Sys_Milliseconds();
- if ( bandwidthChallengeEndTime > time ) {
- // Test is still going on
- return;
- }
- // Its been long enough since we last received bw test msg. So lets send the results to the server
- byte buffer[ idPacketProcessor::MAX_MSG_SIZE ];
- idBitMsg msg( buffer, sizeof( buffer ) );
- // Send total time it took to receive all the msgs
- // (note, subtract net_bw_test_timeout to get 'last recevied bandwidth test packet')
- // (^^ Note if the last packet is fragmented and we never get it, this is technically wrong!)
- int totalTime = ( bandwidthChallengeEndTime - session->GetTitleStorageInt( "net_bw_test_timeout", net_bw_test_timeout.GetInteger() ) ) - bandwidthChallengeStartTime;
- msg.WriteLong( totalTime );
- // Send total number of complete, in order packets we got
- msg.WriteLong( bandwidthChallengeNumGoodSeq );
- // Send the overall average bandwidth in KBS
- // Note that sending the number of good packets is not enough. If the packets going out are fragmented, and we
- // drop fragments, the number of good sequences will be lower than the bandwidth we actually received.
- int totalIncomingBytes = peers[host].packetProc->GetIncomingBytes() - peers[host].bandwidthTestBytes;
- msg.WriteLong( totalIncomingBytes );
- idLib::Printf("^3Finished Bandwidth test: \n");
- idLib::Printf(" Total time: %d\n", totalTime );
- idLib::Printf(" Num good packets: %d\n", bandwidthChallengeNumGoodSeq );
- idLib::Printf(" Total received byes: %d\n\n", totalIncomingBytes );
- bandwidthChallengeStartTime = 0;
- bandwidthChallengeNumGoodSeq = 0;
- QueueReliableMessage( host, RELIABLE_BANDWIDTH_VALUES, msg.GetReadData(), msg.GetSize() );
- }
- /*
- ========================
- idLobby::HandleBandwidhTestValue
- ========================
- */
- void idLobby::HandleBandwidhTestValue( int p, idBitMsg & msg ) {
- if ( !IsHost() ) {
- return;
- }
- idLib::Printf("Received RELIABLE_BANDWIDTH_CHECK %d\n", Sys_Milliseconds() );
- if ( bandwidthChallengeStartTime < 0 || bandwidthChallengeFinished ) {
- idLib::Warning("Received bandwidth test results too early from peer %d", p );
- return;
- }
- int totalTime = msg.ReadLong();
- int totalGoodSeq = msg.ReadLong();
- int totalReceivedBytes = msg.ReadLong();
- // This is the % of complete packets we received. If the packets used in the BWC are big enough to fragment, then pctPackets
- // will be lower than bytesPct (we will have received a larger PCT of overall bandwidth than PCT of full packets received).
- // Im not sure if this is a useful distinction or not, but it may be good to compare against for now.
- float pctPackets = peers[p].bandwidthSequenceNum > 0 ? (float) totalGoodSeq / (float)peers[p].bandwidthSequenceNum : -1.0f;
- // This is the % of total bytes sent/bytes received.
- float bytesPct = peers[p].bandwidthTestBytes > 0 ? (float) totalReceivedBytes / (float)peers[p].bandwidthTestBytes : -1.0f;
- // Calculate overall bandwidth for the test. That is, total amount received over time.
- // We may want to expand this to also factor in an average instantaneous rate.
- // For now we are mostly concerned with culling out poor performing clients
- float peerKBS = -1.0f;
- if ( verify( totalTime > 0 ) ) {
- peerKBS = ( (float)totalReceivedBytes / 1024.0f ) / MS2SEC(totalTime);
- }
- int totalSendTime = peers[p].bandwidthTestLastSendTime - peers[p].bandwidthChallengeStartSendTime;
- float outgoingKBS = -1.0f;
- if ( verify( totalSendTime > 0 ) ) {
- outgoingKBS = ( (float)peers[p].bandwidthTestBytes / 1024.0f ) / MS2SEC(totalSendTime);
- }
- float pctKBS = peerKBS / outgoingKBS;
- bool failedRate = ( pctKBS < session->GetTitleStorageFloat( "net_bw_test_throttle_rate_pct", net_bw_test_throttle_rate_pct.GetFloat() ) );
- bool failedByte = ( bytesPct < session->GetTitleStorageFloat( "net_bw_test_throttle_byte_pct", net_bw_test_throttle_byte_pct.GetFloat() ) );
- bool failedSeq = ( pctPackets < session->GetTitleStorageFloat( "net_bw_test_throttle_seq_pct", net_bw_test_throttle_seq_pct.GetFloat() ) );
- idLib::Printf("^3Finished Bandwidth test %s: \n", GetPeerName(p) );
- idLib::Printf(" Total time: %dms\n", totalTime );
- idLib::Printf(" %sNum good packets: %d (%.2f%)\n", ( failedSeq ? "^1" : "^2" ), totalGoodSeq, pctPackets );
- idLib::Printf(" %sTotal received bytes: %d (%.2f%)\n", ( failedByte ? "^1" : "^2" ), totalReceivedBytes, bytesPct );
- idLib::Printf(" %sEffective downstream: %.2fkbs (host: %.2fkbs) -> %.2f%\n\n", ( failedRate ? "^1" : "^2" ), peerKBS, outgoingKBS, pctKBS );
- // If shittConnection(totalTime, totalGoodSeq/totalSeq, totalReceivedBytes/totalSentBytes)
- // throttle this user:
- // peers[p].throttledSnapRate = baseSnapRate * 2
- if ( failedRate || failedByte || failedSeq ) {
- ThrottlePeerSnapRate( p );
- }
-
- // See if we are finished
- peers[p].bandwidthChallengeResults = true;
- bandwidthChallengeFinished = true;
- for ( int i=0; i < peers.Num(); i++ ) {
- if ( peers[i].bandwidthChallengeSendComplete && !peers[i].bandwidthChallengeResults ) {
- bandwidthChallengeFinished = false;
- }
- }
- if ( bandwidthChallengeFinished ) {
- bandwidthChallengeStartTime = 0;
- }
- }
- /*
- ========================
- idLobby::SendPingValues
- Host only
- Periodically send all peers' pings to all peers (for the UI).
- ========================
- */
- void idLobby::SendPingValues() {
- if ( !verify( IsHost() ) ) {
- // paranoia
- return;
- }
- const int now = Sys_Milliseconds();
- if ( nextSendPingValuesTime > now ) {
- return;
- }
- nextSendPingValuesTime = now + PING_INTERVAL_MS;
- pktPingValues_t packet;
- memset( &packet, 0, sizeof(packet) );
- for( int i = 0; i < peers.Max(); ++i ) {
- if ( i >= peers.Num() ) {
- packet.pings[ i ] = -1;
- } else if ( peers[ i ].IsConnected() ) {
- packet.pings[ i ] = peers[ i ].lastPingRtt;
- } else {
- packet.pings[ i ] = -1;
- }
- }
- byte packetCopy[ sizeof(packet) ];
- idBitMsg msg( packetCopy, sizeof(packetCopy) );
- for( int i = 0; i < peers.Max(); ++i ) {
- msg.WriteShort( packet.pings[ i ] );
- }
- for ( int i = 0; i < peers.Num(); i++ ) {
- if ( peers[ i ].IsConnected() ) {
- QueueReliableMessage( i, RELIABLE_PING_VALUES, msg.GetReadData(), msg.GetSize() );
- }
- }
- }
- /*
- ========================
- idLobby::PumpPings
- Host: Periodically determine the round-trip time for a packet to all peers, and tell everyone
- what everyone else's ping to the host is so they can display it in the UI.
- Client: Indicate to the player when the server hasn't updated the ping values in too long.
- This is usually going to preceed a connection timeout.
- ========================
- */
- void idLobby::PumpPings() {
- if ( IsHost() ) {
- // Calculate ping to all peers
- PingPeers();
- // Send the hosts calculated ping values to each peer to everyone has updated ping times
- SendPingValues();
- // Do bandwidth testing
- ServerUpdateBandwidthTest();
- // Send Migration Data
- SendMigrationGameData();
- } else if ( IsPeer() ) {
- ClientUpdateBandwidthTest();
- if ( lastPingValuesRecvTime + PING_INTERVAL_MS + 1000 < Sys_Milliseconds() && migrationInfo.state == MIGRATE_NONE ) {
- for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
- lobbyUser_t * user = GetLobbyUser( userIndex );
- if ( !verify( user != NULL ) ) {
- continue;
- }
- user->pingMs = 999999;
- }
- }
- }
- }
- /*
- ========================
- idLobby::HandleReliablePing
- ========================
- */
- void idLobby::HandleReliablePing( int p, idBitMsg & msg ) {
- int c, b;
- msg.SaveReadState( c, b );
- pktPing_t ping;
- memset( &ping, 0, sizeof( ping ) );
- if ( !verify( sizeof( ping ) <= msg.GetRemainingData() ) ) {
- NET_VERBOSE_PRINT( "NET: Ignoring ping from peer %i because packet was the wrong size\n", p );
- return;
- }
- ping.timestamp = msg.ReadLong();
- if ( IsHost() ) {
- // we should probably verify here whether or not this ping was solicited or not
- HandlePingReply( p, ping );
- } else {
- // this means the server is requesting a ping, so reply
- msg.RestoreReadState( c, b );
- QueueReliableMessage( p, RELIABLE_PING, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
- }
- }
- /*
- ========================
- idLobby::HandlePingReply
- ========================
- */
- void idLobby::HandlePingReply( int p, const pktPing_t & ping ) {
- const int now = Sys_Milliseconds();
- const int rtt = now - ping.timestamp;
- peers[p].lastPingRtt = rtt;
- for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
- lobbyUser_t * u = GetLobbyUser( userIndex );
- if ( u->peerIndex == p ) {
- u->pingMs = rtt;
- }
- }
- }
- /*
- ========================
- idLobby::HandlePingValues
- ========================
- */
- void idLobby::HandlePingValues( idBitMsg & msg ) {
- pktPingValues_t packet;
- memset( &packet, 0, sizeof( packet ) );
- for( int i = 0; i < peers.Max(); ++i ) {
- packet.pings[ i ] = msg.ReadShort();
- }
- assert( IsPeer() );
- lastPingValuesRecvTime = Sys_Milliseconds();
- for ( int userIndex = 0; userIndex < GetNumLobbyUsers(); ++userIndex ) {
- lobbyUser_t * u = GetLobbyUser( userIndex );
- if ( u->peerIndex != -1 && verify( u->peerIndex >= 0 && u->peerIndex < MAX_PEERS ) ) {
- u->pingMs = packet.pings[ u->peerIndex ];
- } else {
- u->pingMs = 0;
- }
- }
-
- // Stuff our ping in the hosts slot
- if ( peerIndexOnHost != -1 && verify( peerIndexOnHost >= 0 && peerIndexOnHost < MAX_PEERS ) ) {
- peers[host].lastPingRtt = packet.pings[ peerIndexOnHost ];
- } else {
- peers[host].lastPingRtt = 0;
- }
- }
- /*
- ========================
- idLobby::SendAnotherFragment
- Other than connectionless sends, this should be the chokepoint for sending packets to peers.
- ========================
- */
- bool idLobby::SendAnotherFragment( int p ) {
- peer_t & peer = peers[p];
-
- if ( !peer.IsConnected() ) { // Not connected to any mode (party or game), so no need to send
- return false;
- }
-
- if ( !peer.packetProc->HasMoreFragments() ) {
- return false; // No fragments to send for this peer
- }
-
- if ( !CanSendMoreData( p ) ) {
- return false; // We need to throttle the sends so we don't saturate the connection
- }
- int time = Sys_Milliseconds();
- if ( time - peer.lastFragmentSendTime < 2 ) {
- NET_VERBOSE_PRINT("Too soon to send another packet. Delta: %d \n", ( time-peer.lastFragmentSendTime) );
- return false; // Too soon to send another fragment
- }
-
- peer.lastFragmentSendTime = time;
- bool sentFragment = false;
-
- while ( true ) {
- idBitMsg msg;
- // We use the final packet size here because it has been processed, and no more headers will be added
- byte buffer[ idPacketProcessor::MAX_FINAL_PACKET_SIZE ];
- msg.InitWrite( buffer, sizeof( buffer ) );
-
- if ( !peers[p].packetProc->GetSendFragment( time, peers[p].sessionID, msg ) ) {
- break;
- }
- const bool useDirectPort = ( lobbyType == TYPE_GAME_STATE );
- msg.BeginReading();
- sessionCB->SendRawPacket( peers[p].address, msg.GetReadData(), msg.GetSize(), useDirectPort );
- sentFragment = true;
- break; // Comment this out to send all fragments in one burst
- }
- if ( peer.packetProc->HasMoreFragments() ) {
- NET_VERBOSE_PRINT("More packets left after ::SendAnotherFragment\n");
- }
-
- return sentFragment;
- }
- /*
- ========================
- idLobby::CanSendMoreData
- ========================
- */
- bool idLobby::CanSendMoreData( int p ) {
- if ( !verify( p >= 0 && p < peers.Num() ) ) {
- NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not a peer\n", p );
- return false;
- }
- peer_t & peer = peers[p];
- if ( !peer.IsConnected() ) {
- NET_VERBOSE_PRINT( "NET: CanSendMoreData %i NO: not connected\n", p );
- return false;
- }
- return peer.packetProc->CanSendMoreData();
- }
- /*
- ========================
- idLobby::ProcessOutgoingMsg
- ========================
- */
- void idLobby::ProcessOutgoingMsg( int p, const void * data, int size, bool isOOB, int userData ) {
- peer_t & peer = peers[p];
-
- if ( peer.GetConnectionState() != CONNECTION_ESTABLISHED ) {
- idLib::Printf( "peer.GetConnectionState() != CONNECTION_ESTABLISHED\n" );
- return; // Peer not fully connected for this session type, return
- }
- if ( peer.packetProc->HasMoreFragments() ) {
- idLib::Error( "FATAL: Attempt to process a packet while fragments still need to be sent.\n" ); // We can't handle this case
- }
- int currentTime = Sys_Milliseconds();
-
- // if ( currentTime - peer.lastProcTime < 30 ) {
- // idLib::Printf("ProcessOutgoingMsg called within %dms %s\n", (currentTime - peer.lastProcTime), GetLobbyName() );
- // }
- peer.lastProcTime = currentTime;
- if ( !isOOB ) {
- // Keep track of the last time an in-band packet was sent
- // (used for things like knowing when reliables could have been last sent)
- peer.lastInBandProcTime = peer.lastProcTime;
- }
-
- idBitMsg msg;
- msg.InitRead( (byte*)data, size );
- peer.packetProc->ProcessOutgoing( currentTime, msg, isOOB, userData );
- }
- /*
- ========================
- idLobby::ResendReliables
- ========================
- */
- void idLobby::ResendReliables( int p ) {
- peer_t & peer = peers[p];
-
- if ( !peer.IsConnected() ) {
- return;
- }
-
- if ( peer.packetProc->HasMoreFragments() ) {
- return; // We can't send more data while fragments are still being sent out
- }
-
- if ( !CanSendMoreData( p ) ) {
- return;
- }
- int time = Sys_Milliseconds();
- const int DEFAULT_MIN_RESEND = 20; // Quicker resend while not in game to speed up resource transmission acks
- const int DEFAULT_MIN_RESEND_INGAME = 100;
- int resendWait = DEFAULT_MIN_RESEND_INGAME;
- if ( sessionCB->GetState() == idSession::INGAME ) {
- // setup some minimum waits and account for ping
- resendWait = Max( DEFAULT_MIN_RESEND_INGAME, peer.lastPingRtt / 2 );
- if ( lobbyType == TYPE_PARTY ) {
- resendWait = Max( 500, resendWait ); // party session does not need fast frequency at all once in game
- }
- } else {
- // don't trust the ping when still loading stuff
- // need to resend fast to speed up transmission of network decls
- resendWait = DEFAULT_MIN_RESEND;
- }
- if ( time - peer.lastInBandProcTime < resendWait ) {
- // no need to resend reliables if they went out on an in-band packet recently
- return;
- }
- if ( peer.packetProc->NumQueuedReliables() > 0 || peer.packetProc->NeedToSendReliableAck() ) {
- //NET_VERBOSE_PRINT( "NET: ResendReliables %s\n", GetLobbyName() );
- ProcessOutgoingMsg( p, NULL, 0, false, 0 ); // Force an empty unreliable msg so any reliables will get processed as well
- }
- }
- /*
- ========================
- idLobby::PumpPackets
- ========================
- */
- void idLobby::PumpPackets() {
- int newTime = Sys_Milliseconds();
-
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( peers[p].IsConnected() ) {
- peers[p].packetProc->RefreshRates( newTime );
- }
- }
- // Resend reliable msg's (do this before we send out the fragments)
- for ( int p = 0; p < peers.Num(); p++ ) {
- ResendReliables( p );
- }
-
- // If we haven't sent anything to our peers in a long time, make sure to send an empty packet (so our heartbeat gets updated) so we don't get disconnected
- // NOTE - We used to only send these to the host, but the host needs to also send these to clients
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( !peers[p].IsConnected() || peers[p].packetProc->HasMoreFragments() ) {
- continue;
- }
- if ( newTime - peers[p].lastProcTime > 1000 * PEER_HEARTBEAT_IN_SECONDS ) {
- //NET_VERBOSE_PRINT( "NET: ProcessOutgoing Heartbeat %s\n", GetLobbyName() );
- ProcessOutgoingMsg( p, NULL, 0, false, 0 );
- }
- }
- // Send any unsent fragments for each peer (do this last)
- for ( int p = 0; p < peers.Num(); p++ ) {
- SendAnotherFragment( p );
- }
- }
- /*
- ========================
- idLobby::UpdateMatchParms
- ========================
- */
- void idLobby::UpdateMatchParms( const idMatchParameters & p ) {
- if ( !IsHost() ) {
- return;
- }
- parms = p;
- // Update lobbyBackend with parms
- if ( lobbyBackend != NULL ) {
- lobbyBackend->UpdateMatchParms( parms );
- }
-
- SendMatchParmsToPeers();
- }
- /*
- ========================
- idLobby::GetHostUserName
- ========================
- */
- const char * idLobby::GetHostUserName() const {
- if ( !IsLobbyActive() ) {
- return INVALID_LOBBY_USER_NAME;
- }
- return GetPeerName( -1 ); // This will just grab the first user with this peerIndex (which should be the host)
- }
- /*
- ========================
- idLobby::SendReliable
- ========================
- */
- void idLobby::SendReliable( int type, idBitMsg & msg, bool callReceiveReliable /*= true*/, peerMask_t sessionUserMask /*= MAX_UNSIGNED_TYPE( peerMask_t ) */ ) {
- //assert( lobbyType == GetActingGameStateLobbyType() );
- assert( type < 256 ); // QueueReliable only accepts a byte for message type
- // the queuing below sends the whole message
- // I don't know if whole message is a good thing or a bad thing, but if the passed message has been read from already, this is most likely not going to do what the caller expects
- assert( msg.GetReadCount() + msg.GetReadBit() == 0 );
- if ( callReceiveReliable ) {
- // NOTE: this will put the msg's read status to fully read - which is why the assert check is above
- common->NetReceiveReliable( -1, type, msg );
- }
- uint32 sentPeerMask = 0;
- for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
- lobbyUser_t * user = GetLobbyUser( i );
- if ( user->peerIndex == -1 ) {
- continue;
- }
- // We only care about sending these to peers in our party lobby
- if ( user->IsDisconnected() ) {
- continue;
- }
- // Don't sent to a user if they are in the exlusion session user mask
- if ( sessionUserMask != 0 && ( sessionUserMask & ( BIT( i ) ) ) == 0 ) {
- continue;
- }
- const int peerIndex = user->peerIndex;
- if ( peerIndex >= peers.Num() ) {
- continue;
- }
- peer_t & peer = peers[peerIndex];
- if ( !peer.IsConnected() ) {
- continue;
- }
- if ( ( sentPeerMask & ( 1 << user->peerIndex ) ) == 0 ) {
- QueueReliableMessage( user->peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData(), msg.GetSize() );
- sentPeerMask |= 1 << user->peerIndex;
- }
- }
- }
- /*
- ========================
- idLobby::SendReliableToLobbyUser
- can only be used on the server. will take care of calling locally if addressed to player 0
- ========================
- */
- void idLobby::SendReliableToLobbyUser( lobbyUserID_t lobbyUserID, int type, idBitMsg & msg ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- assert( type < 256 ); // QueueReliable only accepts a byte for message type
- assert( IsHost() ); // This function should only be called in the server atm
- const int peerIndex = PeerIndexFromLobbyUser( lobbyUserID );
- if ( peerIndex >= 0 ) {
- // will send the remainder of a message that was started reading through, but not handling a partial byte read
- assert( msg.GetReadBit() == 0 );
- QueueReliableMessage( peerIndex, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
- } else {
- common->NetReceiveReliable( -1, type, msg );
- }
- }
- /*
- ========================
- idLobby::SendReliableToHost
- will make sure to invoke locally if used on the server
- ========================
- */
- void idLobby::SendReliableToHost( int type, idBitMsg & msg ) {
- assert( lobbyType == GetActingGameStateLobbyType() );
- if ( IsHost() ) {
- common->NetReceiveReliable( -1, type, msg );
- } else {
- // will send the remainder of a message that was started reading through, but not handling a partial byte read
- assert( msg.GetReadBit() == 0 );
- QueueReliableMessage( host, idLobby::RELIABLE_GAME_DATA + type, msg.GetReadData() + msg.GetReadCount(), msg.GetRemainingData() );
- }
- }
- /*
- ================================================================================================
- idLobby::reliablePlayerToPlayerHeader_t
- ================================================================================================
- */
- /*
- ========================
- idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t
- ========================
- */
- idLobby::reliablePlayerToPlayerHeader_t::reliablePlayerToPlayerHeader_t() : fromSessionUserIndex( -1 ), toSessionUserIndex( -1 ) {
- }
- /*
- ========================
- idSessionLocal::reliablePlayerToPlayerHeader_t::Read
- ========================
- */
- bool idLobby::reliablePlayerToPlayerHeader_t::Read( idLobby * lobby, idBitMsg & msg ) {
- assert( lobby != NULL );
- lobbyUserID_t lobbyUserIDFrom;
- lobbyUserID_t lobbyUserIDTo;
- lobbyUserIDFrom.ReadFromMsg( msg );
- lobbyUserIDTo.ReadFromMsg( msg );
- fromSessionUserIndex = lobby->GetLobbyUserIndexByID( lobbyUserIDFrom );
- toSessionUserIndex = lobby->GetLobbyUserIndexByID( lobbyUserIDTo );
- if ( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) ) {
- return false;
- }
- if ( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) ) {
- return false;
- }
- return true;
- }
- /*
- ========================
- idLobby::reliablePlayerToPlayerHeader_t::Write
- ========================
- */
- bool idLobby::reliablePlayerToPlayerHeader_t::Write( idLobby * lobby, idBitMsg & msg ) {
-
- if ( !verify( lobby->GetLobbyUser( fromSessionUserIndex ) != NULL ) ) {
- return false;
- }
- if ( !verify( lobby->GetLobbyUser( toSessionUserIndex ) != NULL ) ) {
- return false;
- }
-
- lobby->GetLobbyUser( fromSessionUserIndex )->lobbyUserID.WriteToMsg( msg );
- lobby->GetLobbyUser( toSessionUserIndex )->lobbyUserID.WriteToMsg( msg );
- return true;
- }
- /*
- ========================
- idLobby::GetNumActiveLobbyUsers
- ========================
- */
- int idLobby::GetNumActiveLobbyUsers() const {
- int numActive = 0;
- for ( int i = 0; i < GetNumLobbyUsers(); ++i ) {
- if ( !GetLobbyUser( i )->IsDisconnected() ) {
- numActive++;
- }
- }
- return numActive;
- }
- /*
- ========================
- idLobby::AllPeersInGame
- ========================
- */
- bool idLobby::AllPeersInGame() const {
- assert( lobbyType == GetActingGameStateLobbyType() ); // This function doesn't make sense on a party lobby currently
- for ( int p = 0; p < peers.Num(); p++ ) {
- if ( peers[p].IsConnected() && !peers[p].inGame ) {
- return false;
- }
- }
-
- return true;
- }
- /*
- ========================
- idLobby::PeerIndexFromLobbyUser
- ========================
- */
- int idLobby::PeerIndexFromLobbyUser( lobbyUserID_t lobbyUserID ) const {
- const int lobbyUserIndex = GetLobbyUserIndexByID( lobbyUserID );
- const lobbyUser_t * user = GetLobbyUser( lobbyUserIndex );
- if ( user == NULL ) {
- // This needs to be OK for bot support ( or else add bots at the session level )
- return -1;
- }
- return user->peerIndex;
- }
- /*
- ========================
- idLobby::GetPeerTimeSinceLastPacket
- ========================
- */
- int idLobby::GetPeerTimeSinceLastPacket( int peerIndex ) const {
- if ( peerIndex < 0 ) {
- return 0;
- }
- return Sys_Milliseconds() - peers[peerIndex].lastHeartBeat;
- }
- /*
- ========================
- idLobby::GetActingGameStateLobbyType
- ========================
- */
- idLobby::lobbyType_t idLobby::GetActingGameStateLobbyType() const {
- extern idCVar net_useGameStateLobby;
- return ( net_useGameStateLobby.GetBool() ) ? TYPE_GAME_STATE : TYPE_GAME;
- }
- //========================================================================================================================
- // idLobby::peer_t
- //========================================================================================================================
- /*
- ========================
- idLobby::peer_t::GetConnectionState
- ========================
- */
- idLobby::connectionState_t idLobby::peer_t::GetConnectionState() const {
- return connectionState;
- }
|