/* Copyright (C) 2009-2011 id Software LLC, a ZeniMax Media company. Copyright (C) 2009 Id Software, Inc. This program 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 2 of the License, or (at your option) any later version. This program 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 this program; if not, write to the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #include "IBGlue.h" #include "../doomiphone.h" // Only one game can be set up at a time on a given wireless segment, although // several independent games can be played. // If a valid setupPacket has arrived in the last second, that will be the // displayed game, otherwise the local system starts sending out setupPackets. packetSetup_t setupPacket; int setupPacketFrameNum; int localGameID; // change every time we take over as the sender of setupPackets boolean levelHasBeenLoaded; // determines if "resume game" does a loadGame and exiting does a saveGame menuState_t menuState; menuState_t lastState = 0; color4_t highlightColor = { 128, 128, 128, 255 }; color4_t colorPressed = { 128, 128, 0, 255 }; void SetupEmptyNetGame(); void R_Draw_Blend( int x, int y, int w, int h, color4_t c ) { x *= ((float)displaywidth) / 480.0f; y *= ((float)displayheight) / 320.0f; w *= ((float)displaywidth) / 480.0f; h *= ((float)displayheight) / 320.0f; glDisable( GL_TEXTURE_2D ); glColor4ubv( c ); glBegin( GL_QUADS ); glVertex2i( x, y ); glVertex2i( x+w, y ); glVertex2i( x+w, y+h ); glVertex2i( x, y+h ); glEnd(); glColor3f( 1, 1, 1 ); glEnable( GL_TEXTURE_2D ); } void R_Draw_Fill( int x, int y, int w, int h, color3_t c ) { // as of 2.2 OS, doing a clear with a small scissor rect is MUCH slower // than drawing geometry color4_t c4; c4[0] = c[0]; c4[1] = c[1]; c4[2] = c[2]; c4[3] = 255; R_Draw_Blend( x, y, w, h, c4 ); } /* ================== iphoneSlider Returns true if modified ================== */ #define SF_DISABLED 1 // grey out, don't respond to touches #define SF_INTEGER 2 // don't add percent boolean iphoneSlider( int x, int y, int w, int h, const char *title, cvar_t *cvar, float min, float max, int sliderFlags ) { float textX = x; float textY = y; float textW = w; float textH = h; x *= ((float)displaywidth) / 480.0f; y *= ((float)displayheight) / 320.0f; w *= ((float)displaywidth) / 480.0f; h *= ((float)displayheight) / 320.0f; float value = cvar->value; char str[80]; float f = ( value - min ) / ( max - min ); if ( f < 0 ) { f = 0; } if ( f > 1 ) { f = 1; } // draw the background PK_StretchTexture( PK_FindTexture( "iphone/slider_shadow.tga" ), x, y, w, h ); // draw the current range PK_BindTexture( PK_FindTexture( "iphone/slider_bar.tga" ) ); #if 0 // proportional glBegin( GL_QUADS ); glTexCoord2f( 0.0f, 0.0f ); glVertex2i( x, y ); glTexCoord2f( f, 0.0f ); glVertex2i( x+w*f, y ); glTexCoord2f( f, 1.0f ); glVertex2i( x+w*f, y+h ); glTexCoord2f( 0.0f, 1.0f ); glVertex2i( x, y+h ); glEnd(); #else // dragging thumb glBegin( GL_QUADS ); glTexCoord2f( 0.0f, 0.0f ); glVertex2i( x+w*f-8, y ); glTexCoord2f( 1.0f, 0.0f ); glVertex2i( x+w*f+8, y ); glTexCoord2f( 1.0f, 1.0f ); glVertex2i( x+w*f+8, y+h ); glTexCoord2f( 0.0f, 1.0f ); glVertex2i( x+w*f-8, y+h ); glEnd(); #endif // draw the title and fraction if ( sliderFlags & SF_INTEGER ) { sprintf( str, "%s : %i", title, (int)value ); } else { sprintf( str, "%s : %i%%", title, (int)(f*100+0.5) ); } iphoneCenterText( textX+ textW/2, textY+textH-10, 0.75, str ); // check for touches if ( numTouches > 0 && touches[0][0] >= x && touches[0][0] < x + w && touches[0][1] >= y && touches[0][1] < y+ h ) { float newValue; float delta; f = (float)( touches[0][0] - x ) / w; if ( sliderFlags & SF_INTEGER ) { newValue = rint( min + f * ( max - min ) ); } else { // round to tenths f = (int)( ( f + 0.05 ) * 10 ) * 0.1f; if ( f < 0 ) { f = 0; } if ( f > 1.0 ) { f = 1.0; } newValue = min + f * ( max - min ); } delta = fabs( newValue - cvar->value ); if ( f == 0 && cvar->value == 0 ) { // special case of disable-at-0 } else if ( delta > 0.01 ) { Cvar_SetValue( cvar->name, newValue ); Sound_StartLocalSound( "iphone/slide_01.wav" ); return true; } return false; } return false; } /* ================== BackButton ================== */ ibutton_t btnBack; int BackButton() { if ( !btnBack.texture ) { SetButtonPicsAndSizes( &btnBack, "iphone/back_button.tga", "", 0, 0, 48, 48 ); } return HandleButton( &btnBack ); } void GetMoreLevels( int x, int y ) { // directly to the app store for more levels SysIPhoneOpenURL( "http://phobos.apple.com/WebObjects/MZStore.woa/wa/viewSoftware?id=304694876" ); } /* ================== iphoneMainMenu ================== */ ibutton_t btnResumeGame; ibutton_t btnNewGame; ibutton_t btnControls; ibutton_t btnMultiplayer; ibutton_t btnWWW; ibutton_t btnDemo; void iphoneMainMenu() { // MAIN MENU IS Driven Through Interface Builder. ( Makes for Rapid ProtoTyping ) } /* ================== iphoneControlMenu ================== */ ibutton_t btnSchemes[4]; ibutton_t btnSettings; ibutton_t btnMove; void iphoneControlMenu() { int i; // iphoneCenterText( 240, 16, 0.75, __DATE__" "__TIME__ ); // 112 units between if ( BackButton() ) { menuState = IPM_MAIN; } if ( NewTextButton( &btnSettings, "Settings", 480-128, 0, 128, 48 ) ) { menuState = IPM_OPTIONS; } if ( NewTextButton( &btnMove, "Move Controls", 48 + (480-(128+160+48))/2, 0, 160, 48 ) ) { menuState = IPM_HUDEDIT; lastState = IPM_MAIN; } if ( !btnSchemes[0].texture ) { for ( int i = 0 ; i < 3 ; i++ ) { SetButtonPicsAndSizes( &btnSchemes[i], va("iphone/config_%i.tga",i+1), "", 32 + (96+64) * i, 48 + ( 112 - 64 )*0.5, 96, 64 ); } } for ( i = 0 ; i < 3 ; i++ ) { char str[128]; sprintf( str, "iphone/config_%i.tga", i+1 ); if ( i != controlScheme->value ) { btnSchemes[i].buttonFlags = BF_DIMMED; } else { btnSchemes[i].buttonFlags = 0; } if ( HandleButton( &btnSchemes[i] ) ) { Cvar_SetValue( controlScheme->name, i ); HudSetForScheme( i ); } } iphoneSlider( 20, 160, 440, 40, "Move stick size", stickMove, 64, 128, 0 ); iphoneSlider( 20, 210, 440, 40, "Turn stick size", stickTurn, 64, 128, 0 ); iphoneSlider( 20, 260, 440, 40, "Tilt move speed", tiltMove, 100, 300, 0 ); if ( tiltMove->value == 100 ) { Cvar_SetValue( tiltMove->name, 0 ); } if ( tiltMove->value ) { Cvar_SetValue( tiltTurn->name, 0 ); } #if 0 iphoneSlider( 20, 280, 440, 40, "Tilt turn speed", tiltTurn, 1500, 3500, 0 ); if ( tiltTurn->value == 1500 ) { Cvar_SetValue( tiltTurn->name, 0 ); } if ( tiltTurn->value ) { Cvar_SetValue( tiltMove->name, 0 ); } #endif } void SetupEmptyNetGame() { Com_Printf( "SetupEmptyNetGame()\n" ); // no current setup packet, so initialize with this phone's default values localGameID = SysIphoneMicroseconds(); memset( &setupPacket, 0, sizeof( setupPacket ) ); setupPacket.gameID = localGameID; setupPacket.packetType = PACKET_VERSION_SETUP; setupPacket.map.dataset = mpDataset->value; setupPacket.map.episode = mpEpisode->value; setupPacket.map.map = mpMap->value; setupPacket.map.skill = mpSkill->value; setupPacket.deathmatch = mpDeathmatch->value; setupPacket.timelimit = timeLimit->value; setupPacket.fraglimit = fragLimit->value; setupPacket.playerID[0] = playerID; } /* ================== SendJoinPacket These will be sent to the server ever frame we are in the multiplayer menu. ================== */ void SendJoinPacket() { packetJoin_t pj; pj.packetType = PACKET_VERSION_JOIN; pj.gameID = setupPacket.gameID; pj.playerID = playerID; int r = sendto( gameSocket, &pj, sizeof( pj ), 0, &netServer.address, sizeof( netServer.address ) ); if ( r == -1 ) { struct sockaddr_in *addr = (struct sockaddr_in *)&netServer.address; byte *ip = (byte *)&addr->sin_addr; Com_Printf( "UDP sendTo %i.%i.%i.%i failed: %s\n", ip[0], ip[1], ip[2], ip[3], strerror( errno ) ); close( gameSocket ); gameSocket = -1; } } /* ================== iphoneMultiplayerMenu ================== */ ibutton_t btnCoop; ibutton_t btnDeathmatch; ibutton_t btnMap; ibutton_t btnNetSettings; typedef enum { NM_MAIN, NM_MAP_SELECT, NM_OPTIONS } netMenu_t; netMenu_t netMenu; 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; } } 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; } if ( !btnDeathmatch.texture ) { // initial setup SetButtonPicsAndSizes( &btnDeathmatch, "iphone/deathmatch.tga", "Deathmatch", 4+48, 64, 96, 96 ); SetButtonPicsAndSizes( &btnCoop, "iphone/co-op.tga", "Cooperative", 480-148, 64, 96, 96 ); } 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 ) ); iphoneCenterText( 240, 160, 0.75, str ); 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 if ( NewTextButton( &btnMap, FindMapName( mpDataset->value, mpEpisode->value, mpMap->value ), 64, 0, 480-128, 48 ) ) { 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; } } if ( NewTextButton( &btnNetSettings, "Settings", 240-64, 64+24, 128, 48 ) ) { netMenu = NM_OPTIONS; } for ( int i = 0 ; i < 4 ; i ++ ) { int x = 45 + ( 64+45) * i; int y = 64+128; x *= ((float)displaywidth) / 480.0f; y *= ((float)displayheight) / 320.0f; // 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 ); } iphoneCenterText( 470, 310, 0.75, "*" ); glColor4f( 1,1,1,1 ); } if ( setupPacketFrameNum == iphoneFrameNum ) { iphoneCenterText( 450, 310, 0.75, "*" ); } // 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; if ( NewTextButton( &btnStart, "Start game", 240-80, 320-48, 160, 48 ) ) { setupPacket.startGame = 1; StartNetGame(); TerminateGameService(); // don't advertise for any more new players return; } } else { iphoneCenterText( 240, 320-10, 0.75, "Waiting for server to start the game" ); } } else { byte *ip = (byte *)&gameSocketAddress.sin_addr; iphoneCenterText( 240, 320-10, 0.75, va("Waiting for players on %i.%i.%i.%i", ip[0], ip[1], ip[2], ip[3] ) ); } } 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 ); } /* ================== 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 ); glTexCoord2f( tc[0][0][0], tc[0][0][1] ); glVertex3f( 0, 0, 0.5 ); glTexCoord2f( tc[0][1][0], tc[0][1][1] ); glVertex3f( displaywidth, 0, 0.5 ); glTexCoord2f( tc[0][2][0], tc[0][2][1]+1 ); glVertex3f( 0, displayheight, 0.5 ); glTexCoord2f( tc[0][3][0], tc[0][3][1]+1 ); glVertex3f( displaywidth, displayheight, 0.5 ); 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 ); } } /* =================== iphoneStartMenu =================== */ void iphoneStartMenu() { mapStart_t map; if ( !iphoneMapSelectMenu( &map ) ) { return; } if ( map.map == -1 ) { // hit the back button menuState = IPM_MAIN; return; } StartSinglePlayerGame( map ); } /* =================== iphoneDrawMenus =================== */ void iphoneDrawMenus() { // Because mState is global, i have no clue when it changes. if( lastState != menuState ) { // Menu State has Changed... switch ( menuState ) { case IPM_MAIN: IB_GotoMainMenu(); break; case IPM_MULTIPLAYER: iphoneMultiplayerMenu(); break; case IPM_MAPS: iphoneStartMenu(); break; case IPM_CONTROLS: iphoneControlMenu(); break; case IPM_OPTIONS: iphoneOptionsMenu(); break; case IPM_HUDEDIT: HudEditFrame(); break; } lastState = menuState; } else { if ( menuState == IPM_PACKET_TEST ) { // do this before the slow drawing background to get 60hz update rate iphonePacketTester(); return; } // check for game start in a received setup packet if ( !netgame && setupPacket.startGame ) { if ( StartNetGame() ) { setupPacket.startGame = false; // we aren't in this game return; } } // interactive menus switch ( menuState ) { case IPM_MULTIPLAYER: iphoneMultiplayerMenu(); break; case IPM_MAPS: iphoneStartMenu(); break; case IPM_CONTROLS: iphoneControlMenu(); break; case IPM_OPTIONS: iphoneOptionsMenu(); break; case IPM_HUDEDIT: HudEditFrame(); break; } } }