/* ================== UpdatePeerTiming Calculates one way latency based on local and remote times ================== */ void UpdatePeerTiming( netPeer_t *peer, int remoteMilliseconds ) { peer->lastPacketAsyncTic = asyncTicNum; peer->lastPacketTime = SysIphoneMilliseconds(); peer->lastTimeDelta = abs( remoteMilliseconds - peer->lastPacketTime ); if ( peer->lowestTimeDelta == 0 || peer->lastTimeDelta < peer->lowestTimeDelta ) { peer->lowestTimeDelta = peer->lastTimeDelta; } peer->oneWayLatency = peer->lastTimeDelta - peer->lowestTimeDelta; if ( peer->oneWayLatency < 0 ) { // this can happen if we context switched at a bad time peer->lowestTimeDelta = peer->lastTimeDelta; peer->oneWayLatency = 0; } // printf( "OWL:%i timeDelta:%i lowest:%i\n", peer->oneWayLatency, // peer->lastTimeDelta, peer->lowestTimeDelta ); }
void iphoneMultiplayerMenu() { if ( gameSocket <= 0 ) { // no socket, so no multiplayer TerminateGameService(); // don't advertise for any more new players setupPacket.gameID = 0; // stop sending packets menuState = IPM_MAIN; return; } boolean server = ( setupPacket.gameID == localGameID ); // different screen when selecting a map to play if ( netMenu == NM_MAP_SELECT ) { mapStart_t map; if ( !iphoneMapSelectMenu( &map ) ) { // haven't selected anything yet return; } netMenu = NM_MAIN; if ( map.map != -1 ) { // selected something new, didn't hit the back arrow setupPacket.map = map; } } #ifndef IPAD else if ( netMenu == NM_OPTIONS ) { Cvar_SetValue( fragLimit->name, setupPacket.fraglimit ); if ( iphoneSlider( 104, 64, 272, 40, "frag limit", fragLimit, 0, 20, SF_INTEGER ) ) { if ( server ) { setupPacket.fraglimit = fragLimit->value; } } Cvar_SetValue( timeLimit->name, setupPacket.timelimit ); if ( iphoneSlider( 104, 64+56, 272, 40, "time limit", timeLimit, 0, 20, SF_INTEGER ) ) { if ( server ) { setupPacket.timelimit = timeLimit->value; } } if ( BackButton() ) { netMenu = NM_MAIN; } return; } #else Cvar_SetValue( fragLimit->name, setupPacket.fraglimit ); if ( iphoneSlider( 314, 410, 400, 40, "frag limit", fragLimit, 0, 20, SF_INTEGER ) ) { if ( server ) { setupPacket.fraglimit = fragLimit->value; } } Cvar_SetValue( timeLimit->name, setupPacket.timelimit ); if ( iphoneSlider( 314, 410+58, 400, 40, "time limit", timeLimit, 0, 20, SF_INTEGER ) ) { if ( server ) { setupPacket.timelimit = timeLimit->value; } } #endif if ( !btnDeathmatch.texture ) { // initial setup #ifdef IPAD SetButtonPicsAndSizes( &btnDeathmatch, "iphone/deathmatch.tga", "Deathmatch", 512-128-50, 150, 128, 128 ); SetButtonPicsAndSizes( &btnCoop, "iphone/co-op.tga", "Cooperative", 512+50, 150, 128, 128 ); #else SetButtonPicsAndSizes( &btnDeathmatch, "iphone/deathmatch.tga", "Deathmatch", 4+48, 64, 96, 96 ); SetButtonPicsAndSizes( &btnCoop, "iphone/co-op.tga", "Cooperative", 480-148, 64, 96, 96 ); #endif } if ( BackButton() ) { if ( server ) { TerminateGameService(); // don't advertise for any more new players setupPacket.gameID = 0; // stop sending packets } menuState = IPM_MAIN; } if ( !server ) { // we aren't the server // send our join packet every frame SendJoinPacket(); if ( setupPacketFrameNum < iphoneFrameNum - 30 ) { // haven't received a current server packet char str[1024]; struct sockaddr_in *sin = (struct sockaddr_in *)&netServer.address; byte *ip = (byte *)&sin->sin_addr; sprintf( str, "Joining server at %i.%i.%i.%i:%i\n", ip[0], ip[1], ip[2], ip[3], ntohs( sin->sin_port ) ); #ifdef IPAD iphoneCenterText( 512, 384, 1, str ); #else iphoneCenterText( 240, 160, 0.75, str ); #endif return; } } else { // cull out any players that haven't given us a packet in a couple seconds int now = SysIphoneMilliseconds(); for ( int i = 1 ; i < MAXPLAYERS ; i++ ) { if ( setupPacket.playerID[i] && now - netPlayers[i].peer.lastPacketTime > 1000 ) { printf( "Dropping player %i: last:%i now:%i\n", i, netPlayers[i].peer.lastPacketTime, now ); setupPacket.playerID[i] = 0; } } } // draw the level and allow clicking to change Cvar_SetValue( mpDataset->name, setupPacket.map.dataset ); Cvar_SetValue( mpEpisode->name, setupPacket.map.episode ); Cvar_SetValue( mpMap->name, setupPacket.map.map ); Cvar_SetValue( mpSkill->name, setupPacket.map.skill ); // map select button / display #ifdef IPAD if ( NewTextButton( &btnMap, FindMapName( mpDataset->value, mpEpisode->value, mpMap->value ), 512 - 200, 80, 400, 48 ) ) { #else if ( NewTextButton( &btnMap, FindMapName( mpDataset->value, mpEpisode->value, mpMap->value ), 64, 0, 480-128, 48 ) ) { #endif if ( server ) { // clients can't go into this menu netMenu = NM_MAP_SELECT; } } if ( setupPacket.deathmatch ) { btnDeathmatch.buttonFlags = 0; btnCoop.buttonFlags = BF_DIMMED; } else { btnDeathmatch.buttonFlags = BF_DIMMED; btnCoop.buttonFlags = 0; } if ( HandleButton( &btnDeathmatch ) ) { if ( server ) { Cvar_SetValue( mpDeathmatch->name, 3 ); // weapons stay, items respawn rules setupPacket.deathmatch = mpDeathmatch->value; } } if ( HandleButton( &btnCoop ) ) { if ( server ) { Cvar_SetValue( mpDeathmatch->name, 0 ); setupPacket.deathmatch = mpDeathmatch->value; } } #ifndef IPAD if ( NewTextButton( &btnNetSettings, "Settings", 240-64, 64+24, 128, 48 ) ) { netMenu = NM_OPTIONS; } #endif for ( int i = 0 ; i < 4 ; i ++ ) { #ifdef IPAD int x = 320 + ( 64+45) * i; int y = 64+260; #else int x = 45 + ( 64+45) * i; int y = 64+128; #endif // FIXME: show proper player colors byte color[4][4] = { { 0, 255, 0, 255 }, { 128, 128, 128, 255 }, { 128,64,0, 255 }, {255,0,0, 255 } }; glColor4ubv( color[i] ); PK_DrawTexture( PK_FindTexture( "iphone/multi_backdrop.tga" ), x, y ); glColor4f( 1, 1, 1, 1 ); if ( setupPacket.playerID[i] == playerID ) { // bigger outline for your player slot PK_StretchTexture( PK_FindTexture( "iphone/multi_frame.tga" ), x, y, 64, 64 ); } // draw doom guy face if ( setupPacket.playerID[i] != 0 ) { PK_DrawTexture( PK_FindTexture( "iphone/multi_face.tga" ), x, y ); #if 0 // temp display IP address byte *ip = (byte *)&setupPacket.address[i].sin_addr; iphoneDrawText( x-16, (i&1) ? y+16 : y+48, 0.75, va("%i.%i.%i.%i", ip[0], ip[1], ip[2], ip[3] ) ); #endif } } if ( server ) { // flash a tiny pic when transmitting if ( iphoneFrameNum & 1 ) { glColor4f( 1,1,1,1 ); } else { glColor4f( 0.5,0.5,0.5,1 ); } #ifdef IPAD iphoneCenterText( 1024 - 20, 768 - 20, 1, "*" ); #else iphoneCenterText( 470, 310, 0.75, "*" ); #endif glColor4f( 1,1,1,1 ); } if ( setupPacketFrameNum == iphoneFrameNum ) { #ifdef IPAD iphoneCenterText( 1024 - 40, 768 - 20, 1, "*" ); #else iphoneCenterText( 450, 310, 0.75, "*" ); #endif } // iphoneDrawText( 0, 310, 0.75, va("%i:%i", localGameID, setupPacket.gameID ) ); // only draw the start button if we have at least two players in game int numPlayers = 0; for ( int i = 0 ; i < MAXPLAYERS ; i++ ) { if ( setupPacket.playerID[i] != 0 ) { numPlayers++; } } if ( numPlayers > 1 ) { if ( server ) { static ibutton_t btnStart; #ifdef IPAD if ( NewTextButton( &btnStart, "Start game", 512-80, 768-100, 160, 48) ) { #else if ( NewTextButton( &btnStart, "Start game", 240-80, 320-48, 160, 48 ) ) { #endif setupPacket.startGame = 1; StartNetGame(); TerminateGameService(); // don't advertise for any more new players return; } } else { #ifdef IPAD iphoneCenterText( 512, 68-25, 1, "Waiting for server to start the game" ); #else iphoneCenterText( 240, 320-10, 0.60, "Waiting for server to start the game" ); #endif } } else { byte *ip = (byte *)&gameSocketAddress.sin_addr; #ifdef IPAD iphoneCenterText( 512, 768-25, 1, va("Waiting for players on %i.%i.%i.%i", ip[0], ip[1], ip[2], ip[3] ) ); iphoneCenterText( 512, 27, 0.75, "Attention: Multiplayer requires a WiFi connection"); iphoneCenterText( 512, 50, 0.75, "that doesn't block UDP port 14666"); #else iphoneCenterText( 240, 320-8, 0.60, va("Waiting for players on %i.%i.%i.%i", ip[0], ip[1], ip[2], ip[3] ) ); iphoneCenterText( 240, 320-50, 0.60, "Attention: Multiplayer requires a WiFi connection"); iphoneCenterText( 240, 320-30, 0.60, "that doesn't block UDP port 14666"); #endif } // static ibutton_t btnStart; // NewTextButton( &btnStart, "Start game", 512-80, 768-100, 160, 48); } #ifndef IPAD static ibutton_t optionButtons[2][6]; static ibutton_t defaultsButton; boolean OptionButton( int col, int row, const char *title ) { assert( col >= 0 && col < 2 && row >= 0 && row < 6 ); return NewTextButton( &optionButtons[col][row], title, 10 + 235 * col, 64 + 50 * row, 225, 48 ); } #endif /* ================== iphoneOptionsMenu ================== */ void iphoneOptionsMenu() { if ( BackButton() ) { menuState = IPM_CONTROLS; } boolean musicState = music->value; if ( SysIPhoneOtherAudioIsPlaying() ) { // music always off when ipod music is playing musicState = false; } if ( NewTextButton( &defaultsButton, "Defaults", 240-225/2, 2, 225, 48 ) ) { // reset all cvars except the reverse-landscape mode value float value = revLand->value; Cvar_Reset_f(); Cvar_SetValue( revLand->name, value ); HudSetForScheme(0); iphoneStartMusic(); } if ( OptionButton( 0, 0, autoUse->value ? "Auto use: ON" : "Auto use: OFF" ) ) { Cvar_SetValue( autoUse->name, !autoUse->value ); } if ( OptionButton( 0, 1, statusBar->value ? "Status bar: ON" : "Status bar: OFF" ) ) { Cvar_SetValue( statusBar->name, !statusBar->value ); } if ( OptionButton( 0, 2, touchClick->value ? "Touch click: ON" : "Touch click: OFF" ) ) { Cvar_SetValue( touchClick->name, !touchClick->value ); } if ( OptionButton( 0, 3, messages->value ? "Text messages: ON" : "Text messages: OFF" ) ) { Cvar_SetValue( messages->name, !messages->value ); } if ( OptionButton( 1, 0, drawControls->value ? "Draw controls: ON" : "Draw controls: OFF" ) ) { Cvar_SetValue( drawControls->name, !drawControls->value ); } if ( OptionButton( 1, 1, musicState ? "Music: ON" : "Music: OFF" ) ) { if ( !SysIPhoneOtherAudioIsPlaying() ) { Cvar_SetValue( music->name, !music->value ); if ( music->value ) { iphoneStartMusic(); } else { iphoneStopMusic(); } } } if ( OptionButton( 1, 2, centerSticks->value ? "Center sticks: ON" : "Center sticks: OFF" ) ) { Cvar_SetValue( centerSticks->name, !centerSticks->value ); } if ( OptionButton( 1, 3, rampTurn->value ? "Ramp turn: ON" : "Ramp turn: OFF" ) ) { Cvar_SetValue( rampTurn->name, !rampTurn->value ); } } /* =================== iphoneIntermission The end-of-level switch was just hit, note the state and awards for the map select menu =================== */ void iphoneIntermission( wbstartstruct_t* wb ) { if ( deathmatch || netgame ) { // no achievements in deathmatch mode return; } // find the current episode / map combination // if a mapStat_t doesn't exist for this yet, create one // mark this level / skill combination as tried mapStats_t *cms = FindMapStats( playState.map.dataset, playState.map.episode, playState.map.map, true ); if ( !cms ) { return; } int skill = playState.map.skill; cms->completionFlags[skill] |= MF_COMPLETED; // add the awards if ( wb->plyr[0].stime < wb->partime ) { cms->completionFlags[skill] |= MF_TIME; } int numkills = 0; int numsecrets = 0; int numitems = 0; for ( int i = 0 ; i < MAXPLAYERS ; i++ ) { if ( wb->plyr[i].in ) { numkills += wb->plyr[i].skills; numitems += wb->plyr[i].sitems; numsecrets += wb->plyr[i].ssecret; } } if ( numkills >= wb->maxkills ) { cms->completionFlags[skill] |= MF_KILLS; } if ( numitems >= wb->maxitems ) { cms->completionFlags[skill] |= MF_TREASURE; } if ( numsecrets >= wb->maxsecret ) { cms->completionFlags[skill] |= MF_SECRETS; } } /* =================== iphoneStartLevel Do a savegame with the current state =================== */ void iphoneStartLevel() { if ( deathmatch || netgame ) { // no achievements in deathmatch mode // reset the levelTimer if ( levelTimer && setupPacket.timelimit > 0 ) { // 30 hz, minutes levelTimeCount = setupPacket.timelimit * 30 * 60; } return; } playState.map.map = gamemap; // automatic save game G_SaveGame( 0, "entersave" ); G_DoSaveGame(true); // mark this level as tried mapStats_t *cms = FindMapStats( playState.map.dataset, playState.map.episode, playState.map.map, true ); if ( cms ) { cms->completionFlags[playState.map.skill] |= MF_TRIED; } } /* =================== DrawLiveBackground Draw a randomish moving cloudy background =================== */ void DrawLiveBackground() { static float bgVectors[2][2] = { { 0.01, 0.015 }, { -0.01, -0.02 } }; float fade[2]; // slide and fade a couple textures around static float tc[2][4][2]; for ( int i = 0 ; i < 2 ; i++ ) { int ofs = iphoneFrameNum + i * 32; float dist = ( ofs & 63 ); for ( int j = 0 ; j < 2 ; j ++ ) { for ( int k = 0 ; k < 2 ; k++ ) { if ( rand()&1 ) { if ( bgVectors[j][k] < 0.03 ) { bgVectors[j][k] += 0.0001; } } else { if ( bgVectors[j][k] > -0.03 ) { bgVectors[j][k] -= 0.0001; } } } } fade[i] = sin( ( dist - 16 ) / 32.0 * M_PI ) * 0.5 + 0.5; fade[i] *= 0.7; for ( int j = 0 ; j < 2 ; j++ ) { tc[i][0][j] += bgVectors[i][j]; tc[i][0][j] -= floor( tc[i][0][j] ); } tc[i][1][0] = tc[i][0][0]+1; tc[i][1][1] = tc[i][0][1]+0; tc[i][2][0] = tc[i][0][0]+0; tc[i][2][1] = tc[i][0][1]+1; tc[i][3][0] = tc[i][0][0]+1; tc[i][3][1] = tc[i][0][1]+1; } // Fill rate performance is an issue just for two scrolling layers under // modest GUI objects. Using a PVR2 texture and a single multitexture // pass helps. If all the GUI objects were drawn with depth buffering, // the surface rejection would help out, but bumping depth after every // draw would be a bit of a chore. #if 0 glClear( GL_DEPTH_BUFFER_BIT ); glDepthMask( 1 ); // write the depth buffer glEnable( GL_DEPTH_TEST ); // depth test this background #endif PK_BindTexture( PK_FindTexture( "iphone/livetile_1.tga" ) ); glDisable( GL_BLEND ); glDisable( GL_DEPTH_TEST ); // multitexture setup glActiveTexture( GL_TEXTURE1 ); glClientActiveTexture( GL_TEXTURE1 ); glEnable( GL_TEXTURE_2D ); PK_BindTexture( PK_FindTexture( "iphone/livetile_1.tga" ) ); glTexCoordPointer( 2, GL_FLOAT, 8, tc[1][0] ); glEnableClientState( GL_TEXTURE_COORD_ARRAY ); glTexEnvf( GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_ADD ); // glColor4f( fade[0], fade[0], fade[0], fade[1] ); glBegin( GL_TRIANGLE_STRIP ); #ifdef IPAD glTexCoord2f( tc[0][0][0], tc[0][0][1] ); glVertex3f( 0, 0, 0.5 ); glTexCoord2f( tc[0][1][0], tc[0][1][1] ); glVertex3f( 1024, 0, 0.5 ); glTexCoord2f( tc[0][2][0], tc[0][2][1]+1 ); glVertex3f( 0, 768, 0.5 ); glTexCoord2f( tc[0][3][0], tc[0][3][1]+1 ); glVertex3f( 1024, 768, 0.5 ); #else glTexCoord2f( tc[0][0][0], tc[0][0][1] ); glVertex3f( 0, 0, 0.5 ); glTexCoord2f( tc[0][1][0], tc[0][1][1] ); glVertex3f( 480, 0, 0.5 ); glTexCoord2f( tc[0][2][0], tc[0][2][1]+1 ); glVertex3f( 0, 320, 0.5 ); glTexCoord2f( tc[0][3][0], tc[0][3][1]+1 ); glVertex3f( 480, 320, 0.5 ); #endif glEnd(); // unbind the second texture glBindTexture( GL_TEXTURE_2D, 0 ); glDisable( GL_TEXTURE_2D ); glDisableClientState( GL_TEXTURE_COORD_ARRAY ); glActiveTexture( GL_TEXTURE0 ); glClientActiveTexture( GL_TEXTURE0 ); glColor4f( 1, 1, 1, 1 ); glEnable( GL_BLEND ); #if 0 // Enable depth test, but not depth writes, so the tile dorting // minimizes the amount of time drawing the background when it // is mostly covered. glEnable( GL_DEPTH_TEST ); glDepthMask( 0 ); #endif } #define MAX_PACKET_LOG 64 int currentPacketLog; int packetLogMsec[MAX_PACKET_LOG]; void iphonePacketTester() { glClear( GL_COLOR_BUFFER_BIT ); if ( BackButton() ) { menuState = IPM_MAIN; return; } struct sockaddr_in sender; unsigned senderLen = sizeof( sender ); byte buffer[1024]; while( 1 ) { int r = recvfrom( gameSocket, buffer, sizeof( buffer ), 0, (struct sockaddr *)&sender, &senderLen ); if ( r == -1 ) { break; } packetSetup_t *sp = (packetSetup_t *)buffer; if ( sp->sendCount == setupPacket.sendCount ) { Com_Printf( "Duplicated receive: %i\n", sp->sendCount ); } else if ( sp->sendCount < setupPacket.sendCount ) { Com_Printf( "Out of order receive: %i < %i\n", sp->sendCount, setupPacket.sendCount ); } else if ( sp->sendCount > setupPacket.sendCount + 1 ) { Com_Printf( "Dropped %i packets before %i\n", sp->sendCount - 1 - setupPacket.sendCount, sp->sendCount ); } setupPacket = *sp; packetLogMsec[currentPacketLog&(MAX_PACKET_LOG-1)] = SysIphoneMilliseconds(); currentPacketLog++; } color4_t activeColor = { 0, 255, 0, 255 }; for ( int i = 1 ; i < MAX_PACKET_LOG ; i++ ) { int t1 = packetLogMsec[(currentPacketLog - i)&(MAX_PACKET_LOG-1)]; int t2 = packetLogMsec[(currentPacketLog - i - 1)&(MAX_PACKET_LOG-1)]; int msec = t1 - t2; R_Draw_Fill( 0, i * 4, msec, 2, activeColor ); } }
/* ================== iphoneAsyncTic This is called by a 30hz scheduled timeer in the main application thread. Commands are generated and sent to the packet server on this regular basis, regardless of the frame rate held by iphoneFrame(). This thread should be higher priority, so it always interrupts the game thread. It might be nice to run the game tics here, but the rendering code is not thread safe with the game, so it isn't an option. ================== */ void iphoneAsyncTic() { // log our timing accuracy (seems to be within a few msec -- not bad) static int prev; int now = SysIphoneMilliseconds(); asyncStats_t *stats = &asyncStats[asyncTicNum&(MAX_ASYNC_LOGS-1)]; asyncTicNum++; memset( stats, 0, sizeof( *stats ) ); stats->msecFromLast = now - prev; stats->msecToExecute = 0; prev = now; // don't generate any commands while loading levels if ( iphoneFrameNum == levelLoadFrameNum ) { return; } int afterLock = SysIphoneMilliseconds(); // latch the current touches for processing for ( int i = 0 ; i < MAX_TOUCHES ; i++ ) { touch_t *t = &sysTouches[i]; gameTouches[i] = *t; // handle the special case of a touch that went down and up // inside a single frame if ( t->stateCount == -1 ) { gameTouches[i].stateCount = 1; t->stateCount = 0; t->down = false; } else { t->stateCount++; } } //--------------------------------- // Create local user command // // We always create one, but it might not wind up being used for a game // tic if it doesn't make it to the server at the right time. //--------------------------------- ticcmd_t cmd; iphoneBuildTiccmd( &cmd ); //--------------------------------- // If we are a client, send our command to the server //--------------------------------- if ( consoleplayer != 0 ) { if ( gameID != 0 && netgame && !netGameFailure ) { stats->latency = packetSequence - lastServerPacket.packetAcknowledge; if ( ShouldSendPacket( &netServer, packetSequence - lastServerPacket.packetAcknowledge ) ) { packetClient_t cp; memset( &cp, 0, sizeof( cp ) ); cp.packetType = PACKET_VERSION_CLIENT; cp.gameID = gameID; cp.packetAcknowledge = lastServerPacket.packetSequence; cp.milliseconds = SysIphoneMilliseconds(); cp.packetSequence = packetSequence++; cp.consoleplayer = consoleplayer; cp.gametic = gametic; cp.cmd = cmd; idGameCenter::SendPacketToPlayerUnreliable( serverGameCenterID, &cp, sizeof ( cp ) ); } } } else { // take our command directly netPlayers[0].pc.cmd = cmd; netPlayers[0].pc.gametic = gametic; netPlayers[0].peer.lastPacketTime = now; //--------------------------------- // Decide if we want to latch the current commands for execution by the game // //--------------------------------- int ticIndex = maketic & BACKUPTICMASK; int worstTic = gametic; for ( int i = 0 ; i < MAXPLAYERS ; i++ ) { if ( playeringame[i] ) { netcmds[i][ticIndex] = netPlayers[i].pc.cmd; if ( netPlayers[i].pc.gametic < worstTic ) { worstTic = netPlayers[i].pc.gametic; } } } // only let the server get a few tics ahead of any client, so if // anyone is having significant net delivery problems, everyone will // stall instead of losing the player. If this is too small, then // every little hitch that any player gets will cause everyone to hitch. if ( maketic - worstTic < netBuffer->value ) { maketic++; } //--------------------------------- // Build server packets to send to clients // // Always send out the current command set over the network // even if we didn't create a new command, in case we are just // recovering from a lot of dropped packets. //--------------------------------- if ( netgame && !netGameFailure ) { // since we are sampling a shared wireless network, any of the player's // latencies should be a good enough metric stats->latency = packetSequence - netPlayers[1].pc.packetAcknowledge; if ( ShouldSendPacket( &netPlayers[1].peer, stats->latency ) ) { packetServer_t gp; memset( &gp, 0, sizeof( gp ) ); gp.packetType = PACKET_VERSION_SERVER; gp.gameID = gameID; gp.packetSequence = packetSequence++; gp.maketic = maketic; for ( int i = 0; i < MAXPLAYERS; ++i ) { gp.playersInGame[i] = playeringame[i]; } memcpy( gp.netcmds, netcmds, sizeof( gp.netcmds ) ); //--------------------------------- // Send network packets to the clients //--------------------------------- for ( int i = 1 ; i < MAXPLAYERS ; i++ ) { if ( !playeringame[i] ) { continue; } netPlayer_t *np = &netPlayers[i]; // only send over the ticcmd that this client needs gp.starttic = np->pc.gametic; ticcmd_t *cmd_p = gp.netcmds; for ( int j = gp.starttic ; j < gp.maketic ; j++ ) { for ( int k = 0 ; k < MAXPLAYERS ; k++ ) { if ( playeringame[k] ) { *cmd_p++ = netcmds[k][j&BACKUPTICMASK]; } } } int packetSize = (byte *)cmd_p - (byte *)&gp; (void)packetSize; // use the most recent tic that both the client and // server have run gp.consistancyTic = np->pc.gametic < gametic ? np->pc.gametic : gametic; gp.consistancyTic--; for ( int j = 0 ; j < MAXPLAYERS ; j++ ) { gp.consistancy[j] = consistancy[j][gp.consistancyTic&BACKUPTICMASK]; } gp.packetAcknowledge = np->pc.packetSequence; gp.milliseconds = SysIphoneMilliseconds(); // transmit the packet const std::string theClient = playerIndexToIDMap[i]; idGameCenter::SendPacketToPlayerUnreliable( theClient, &gp, packetSize ); } } } } stats->msecToExecute = SysIphoneMilliseconds() - now; if ( stats->msecToExecute > 6 ) { printf( "long asyncTic %i: %i msec (%i in lock), %i packets\n", asyncTicNum - 1, stats->msecToExecute, afterLock - now, stats->received ); } }
/* ================== iphoneProcessPacket A packet has been received over WiFi or bluetooth ================== */ void iphoneProcessPacket( const struct sockaddr *from, const void *data, int len ) { if ( len < 4 ) { printf( "discarding packet because len = %i.\n", len ); return; } int packetID = *(int *)data; if ( !netgame ) { // setup and join are only processed while in the menu system if ( packetID == PACKET_VERSION_SETUP ) { if ( localGameID == setupPacket.gameID ) { // if we are sending packets, always ignore other setup packets printf( "discarding setup packet because we are the server\n" ); return; } setupPacketFrameNum = iphoneFrameNum; // save this packet // printf( "valid setup packet\n" ); setupPacket = *(packetSetup_t *)data; return; } if ( packetID == PACKET_VERSION_JOIN ) { // we should only process join packets if we are in the multiplayer // menu and running the current game if ( menuState != IPM_MULTIPLAYER ) { printf( "discarding join packet because not in IPM_MULTIPLAYER\n" ); return; } if ( setupPacket.gameID != localGameID ) { printf( "discarding join packet because we aren't the server\n" ); return; } packetJoin_t *pj = (packetJoin_t *)data; if ( pj->playerID == 0 ) { // should never happen printf( "discarding join packet because playerID is 0\n" ); return; } // add this player int i; for ( i = 0 ; i < MAXPLAYERS ; i++ ) { if ( setupPacket.playerID[i] == pj->playerID ) { netPlayers[i].peer.lastPacketTime = SysIphoneMilliseconds(); break; } } if ( i == MAXPLAYERS ) { // not in yet, add if possible for ( i = 0 ; i < MAXPLAYERS ; i++ ) { if ( setupPacket.playerID[i] == 0 ) { setupPacket.playerID[i] = pj->playerID; netPlayers[i].peer.address = *from; netPlayers[i].peer.lastPacketTime = SysIphoneMilliseconds(); break; } } // if all players are active, the new join gets ignored } // printf( "valid join packet\n" ); return; } // no other packets are processed unless we are in the game printf( "discarding packet with id 0x%x when not in netgame\n", packetID ); return; } // the following are only for running games if ( consoleplayer == 0 ) { // we are the server, and should only receive packetClient_t if ( packetID != PACKET_VERSION_CLIENT ) { static boolean typeErrorPrinted; if ( !typeErrorPrinted ) { typeErrorPrinted = true; printf( "Packet received with type 0x%x instead of 0x%x\n", packetID, PACKET_VERSION_CLIENT ); } return; } packetClient_t *pc = (packetClient_t *)data; if ( len != sizeof( *pc ) ) { // this should always be an exact length match return; } if ( pc->gameID != gameID ) { static boolean gameErrorPrinted; if ( !gameErrorPrinted ) { gameErrorPrinted = true; printf( "Packet received with gameID 0x%x instead of 0x%x\n", pc->gameID, gameID ); } return; } assert( pc->consoleplayer > 0 && pc->consoleplayer < MAXPLAYERS ); netPlayer_t *np = &netPlayers[pc->consoleplayer]; if ( np->pc.packetSequence >= pc->packetSequence ) { printf( "Out of order or duplicated packet from player %i\n", pc->consoleplayer ); return; } np->peer.currentPingTics = packetSequence - pc->packetAcknowledge; if ( np->pc.packetSequence != pc->packetSequence - 1 ) { printf( "Dropped %i packets from player %i\n", pc->packetSequence - 1 - np->pc.packetSequence, pc->consoleplayer ); } // good packet from client np->pc = *pc; UpdatePeerTiming( &np->peer, np->pc.milliseconds ); } else { // we are a client, and should only receive server packets if ( packetID != PACKET_VERSION_SERVER ) { static boolean typeErrorPrinted; if ( !typeErrorPrinted ) { typeErrorPrinted = true; printf( "Packet received with type 0x%x instead of 0x%x\n", packetID, PACKET_VERSION_CLIENT ); } return; } packetServer_t *ps = (packetServer_t *)data; if ( len > sizeof( *ps ) ) { // packets will usually have less ticcmd_t, but never more return; } if ( ps->gameID != gameID ) { static boolean gameErrorPrinted; if ( !gameErrorPrinted ) { gameErrorPrinted = true; printf( "Packet received with gameID 0x%x instead of 0x%x\n", ps->gameID, gameID ); } return; } if ( ps->packetSequence <= lastServerPacket.packetSequence ) { printf( "Out of order or duplicated packet from server: %i <= %i\n", ps->packetSequence , lastServerPacket.packetSequence ); return; } int drop = ps->packetSequence - (lastServerPacket.packetSequence + 1); if ( drop > 0 ) { printf( "Dropped %i packets from server\n", drop ); } // good packet from server memcpy( &lastServerPacket, ps, len ); UpdatePeerTiming( &netServer, ps->milliseconds ); netServer.currentPingTics = packetSequence - ps->packetAcknowledge; // It is possible to have a client run a tic that hasn't been run yet on the game // server, since the server can be generating cmds and sending packets while // its game frame is hitched for an image load, so this is not an error condition. // assert( ps->gametic >= gametic ); // this should never happen assert( ps->maketic >= maketic ); // if a ticcmd_t that we need has permanently rolled off the end, we are hosed. // This shouldn't happen, since we don't create commands if all the clients // haven't processed most of the ones already sent. if ( ps->maketic - gametic >= BACKUPTICS ) { printf( "BACKUPTICS exceeded: ps->maketic %i, gametic %i\n", ps->maketic, gametic ); netGameFailure = NF_INTERRUPTED; } // move over the new commands // it is possible that some early frames of these are redundant, due // to packets crossing in flight. ticcmd_t *cmd_p = ps->netcmds; for ( int i = ps->starttic ; i < ps->maketic ; i++ ) { for ( int j = 0 ; j < MAXPLAYERS ; j++ ) { if ( playeringame[j] ) { netcmds[j][i&BACKUPTICMASK] = *cmd_p++; } } } // copy this after the cmds have been updated maketic = ps->maketic; // See if anyone has disconnected. for ( int i = 0; i < MAXPLAYERS; ++i ) { playeringame[i] = ps->playersInGame[i]; } // check consistancy for all in-game players on the most // recent gametic that the server knew this client had run int checkTic = ps->consistancyTic; assert( checkTic > gametic - BACKUPTICS ); // if older than this, we have lost the data checkTic &= BACKUPTICMASK; for ( int i = 0 ; i < MAXPLAYERS ; i++ ) { if ( playeringame[i] ) { if ( ps->consistancy[i] != consistancy[i][checkTic] ) { printf( "ConsistancyFailure for player %i on consistancyTic %i\n", i, ps->consistancyTic ); netGameFailure = NF_CONSISTANCY; } } } } }
void iphoneStartup() { int start = SysIphoneMilliseconds(); // microseconds will be plenty random for playerID and localGameID playerID = localGameID = SysIphoneMicroseconds(); InitImmediateModeGL(); // init OpenAL before pak file, so the pak file can // make all the al static buffers Sound_Init(); char buffer[1028]; sprintf( buffer, "%s/base.iPack", SysIphoneGetAppDir() ); // get our new-style pak file PK_Init( buffer ); // register console commands Cmd_AddCommand( "listcvars", Cvar_List_f ); Cmd_AddCommand( "resetcvars", Cvar_Reset_f ); Cmd_AddCommand( "resetmaps", ResetMaps_f ); Cmd_AddCommand( "listcmds", Cmd_ListCommands_f ); Cmd_AddCommand( "give", Give_f ); Cmd_AddCommand( "god", God_f ); Cmd_AddCommand( "mail", EmailConsole ); //gsh, mails the console to id // register console variables Cvar_Get( "version", va( "%3.1f %s %s", DOOM_IPHONE_VERSION, __DATE__, __TIME__ ), 0 ); freeLevelOfWeek = Cvar_Get("freeLevelOfWeek", "0", 0 ); skill = Cvar_Get( "skill", "1", CVAR_ARCHIVE ); episode = Cvar_Get( "episode", "0", CVAR_ARCHIVE ); controlScheme = Cvar_Get( "controlScheme", "0", CVAR_ARCHIVE ); stickTurn = Cvar_Get( "stickTurn", "128", CVAR_ARCHIVE ); stickMove = Cvar_Get( "stickMove", "128", CVAR_ARCHIVE ); stickDeadBand = Cvar_Get( "stickDeadBand", "0.05", CVAR_ARCHIVE ); rotorTurn = Cvar_Get( "rotorTurn", "50000", CVAR_ARCHIVE ); tiltTurn = Cvar_Get( "tiltTurn", "0", CVAR_ARCHIVE ); tiltMove = Cvar_Get( "tiltMove", "0", CVAR_ARCHIVE ); tiltDeadBand = Cvar_Get( "tiltDeadBand", "0.08", CVAR_ARCHIVE ); tiltAverages = Cvar_Get( "tiltAverages", "3", CVAR_ARCHIVE ); centerSticks = Cvar_Get( "centerSticks", "1", CVAR_ARCHIVE ); rampTurn = Cvar_Get( "rampTurn", "1", CVAR_ARCHIVE ); music = Cvar_Get( "music", "1", CVAR_ARCHIVE ); cropSprites = Cvar_Get( "cropSprites", "1", 0 ); mapScale = Cvar_Get( "mapScale", "10", CVAR_ARCHIVE ); drawControls = Cvar_Get( "drawControls", "1", CVAR_ARCHIVE ); autoUse = Cvar_Get( "autoUse", "1", CVAR_ARCHIVE ); statusBar = Cvar_Get( "statusBar", "1", CVAR_ARCHIVE ); touchClick = Cvar_Get( "touchClick", "1", CVAR_ARCHIVE ); messages = Cvar_Get( "messages", "1", CVAR_ARCHIVE ); mapSelectY = Cvar_Get( "mapSelectY", "0", CVAR_ARCHIVE ); miniNet = Cvar_Get( "miniNet", "1", CVAR_ARCHIVE ); // multiplayer setup timeLimit = Cvar_Get( "timeLimit", "0", CVAR_ARCHIVE ); fragLimit = Cvar_Get( "fragLimit", "5", CVAR_ARCHIVE ); mpDeathmatch = Cvar_Get( "mpDeathmatch", "0", CVAR_ARCHIVE ); mpDataset = Cvar_Get( "mpDataset", "0", CVAR_ARCHIVE ); mpEpisode = Cvar_Get( "mpEpisode", "1", CVAR_ARCHIVE ); mpSkill = Cvar_Get( "mpSkill", "1", CVAR_ARCHIVE ); mpMap = Cvar_Get( "mpMap", "1", CVAR_ARCHIVE ); mpExpansion = Cvar_Get( "mpExpansion", "0", CVAR_ARCHIVE | CVAR_NOSET ); // debug tools showTilt = Cvar_Get( "showTilt", "-1", 0 ); showTime = Cvar_Get( "showTime", "0", 0 ); showNet = Cvar_Get( "showNet", "0", 0 ); showSound = Cvar_Get( "showSound", "0", 0 ); noBlend = Cvar_Get( "noBlend", "0", 0 ); // disable the damae blends for screenshots glfinish = Cvar_Get( "glfinish", "0", 0 ); throttle = Cvar_Get( "throttle", "0", 0 ); // network packet throttle enable // Was origiinally 4. Trying different values to help internet play. netBuffer = Cvar_Get( "netBuffer", "12", 0 ); // max tics to buffer ahead // load the archived cvars Cmd_ExecuteFile( va( "%s/config.cfg", SysIphoneGetDocDir() ) ); // start the intro music if it wasn't disabled with the music cvar iphonePlayMusic( "intro" ); // iphonePlayMusic( "e1m1" ); // these should get overwritten by the config loading memset( &playState, 0, sizeof( playState ) ); playState.map.skill = 1; playState.map.episode = 1; playState.map.map = 1; HudSetForScheme( 0 ); // load the binary config file FILE *f = fopen( va( "%s/binaryConfig.bin", SysIphoneGetDocDir() ), "rb" ); if ( f ) { int version; version = 0; fread( &version, 1, sizeof( version ), f ); if ( version != VERSION_BCONFIG ) { Com_Printf( "Binary config file bad version.\n" ); } else { fread( &playState, 1, sizeof( playState ), f ); fread( &huds, 1, sizeof( huds ), f ); version = 0; fread( &version, 1, sizeof( version ), f ); if ( version != VERSION_BCONFIG ) { Com_Error( "Binary config file bad trailing version.\n" ); } } fclose( f ); } Com_Printf( "startup time: %i msec\n", SysIphoneMilliseconds() - start ); start = SysIphoneMilliseconds(); // the texnums might have been different in the savegame HudSetTexnums(); arialFontTexture = PK_FindTexture( "iphone/arialImageLAL.tga" ); Com_Printf( "preloadBeforePlay(): %i msec\n", SysIphoneMilliseconds() - start ); // prBoom seems to draw the static pic screens without setting up 2D, causing // a bad first frame iphoneSet2D(); menuState = IPM_MAIN; lastState = IPM_MAIN; #if 0 // jump right to the save spot for debugging ResumeGame(); #endif }