123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423 |
- /*
-
- Copyright (C) 2009-2011 id Software LLC, a ZeniMax Media company.
-
- 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 "DoomGameCenterMatch.h"
- #include "doomiphone.h"
- #include "ios/ios_interface.h"
- #include <cstdio>
- #include <string>
- #include <algorithm>
- DoomGameCenterMatch gDoomGameCenterMatch;
- std::string serverGameCenterID;
- std::tr1::array<std::string, 4> playerIndexToIDMap;
- namespace {
- bool IsServer() {
- return setupPacket.gameID == localGameID;
- }
-
- int numPlayersToJoin = 0;
- }
- DoomGameCenterMatch::~DoomGameCenterMatch() {
- }
- void DoomGameCenterMatch::createdMatchImpl() {
- std::printf( "Created a DOOM match!\n" );
- }
- void DoomGameCenterMatch::allPlayersConnectedImpl( std::vector<std::string> connectedPlayerIDs ) {
- std::printf( "All players connected to a DOOM match!\n" );
-
- // Send an initial setup packet if this is the server, this will cause clients to
- // send their join packets.
- if ( IsServer() ) {
- printf( "Server broadcasting initial setup packet.\n" );
-
- numPlayersToJoin = connectedPlayerIDs.size();
- assert( numPlayersToJoin > 0 );
- SendGameCenterSetup();
- }
- }
- void DoomGameCenterMatch::playerConnectedImpl( std::string playerIdentifier ) {
- std::printf( "Player %s connected!\n", playerIdentifier.c_str() );
- }
- void DoomGameCenterMatch::playerDisconnectedImpl( std::string playerIdentifier ) {
- std::printf( "Player %s disconnected!\n", playerIdentifier.c_str() );
-
- if ( IsServer() ) {
- // Only the server tracks disconnected players. The next server packet sent to each
- // client will have the new playeringame[MAXPLAYERS] info.
- for ( int i = 0; i < MAXPLAYERS; ++i ) {
- if ( playerIndexToIDMap[i] == playerIdentifier ) {
- playeringame[i] = false;
- }
- }
- } else {
- // If we are a client, and the server is the one that disconnected, we're hosed.
- if ( !netGameFailure && playerIdentifier == serverGameCenterID ) {
- netGameFailure = NF_LOST_SERVER;
-
- idGameCenter::DisconnectFromMatch();
-
- ShowSystemAlert( "#LostServerTitle", "#LostServerMessage" );
-
- iphoneMainMenu();
- }
- }
- }
- template<class Type, size_t Size>
- const Type * end( const Type (&testArray)[Size] ) {
- return testArray + Size;
- }
- template<class Type, size_t Size>
- bool ArrayContains( const Type (&testArray)[Size], const Type & value ) {
- return std::find( testArray, end( testArray ), value ) != end( testArray );
- }
- /*
- ==================
- DoomGameCenterMatch::receivedDataImpl
-
- A packet has been received from Game Center. Because the Game Center "callback" actually runs
- on the main thread, it should be safe to directly process the packets here. This should only
- be called in between displaylink updates.
- ==================
- */
- void DoomGameCenterMatch::receivedDataImpl( std::string fromPlayerID, const void * data, int numBytes ) {
-
- if ( numBytes < 4 ) {
- std::printf( "discarding packet because numBytes = %i.\n", numBytes );
- return;
- }
-
- int packetID = *(int *)data;
-
- if ( packetID == PACKET_VERSION_SETUP ) {
- std::printf( "Received a setup packet!\n" );
-
- // Only the server sends setup packets, and we need to keep track of it's ID.
- serverGameCenterID = fromPlayerID;
-
- 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
- setupPacket = *(packetSetup_t *)data;
-
- // Send a join packet to the server so that it's aware of this client, if this client
- // isn't already in the list.
- if ( !ArrayContains( setupPacket.playerID, localGameID ) ) {
- printf( "This client has not notified the server yet - sending join packet.\n" );
- SendJoinPacket();
- }
-
- // check for game start in a received setup packet
- if ( !netgame && setupPacket.startGame ) {
- StartupWithCorrectWads( setupPacket.map.dataset );
-
- ShowGLView();
-
- if ( StartNetGame() ) {
- setupPacket.startGame = false;
- // we aren't in this game
- }
-
-
- }
-
- return;
- }
-
- if ( packetID == PACKET_VERSION_JOIN ) {
- // we should only process join packets if we are running the current game
- 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;
-
- // Save this client's GameCenter ID to send it packets later.
- playerIndexToIDMap[i] = fromPlayerID;
-
- //netPlayers[i].peer.address = *from;
- netPlayers[i].peer.lastPacketTime = SysIphoneMilliseconds();
-
- --numPlayersToJoin;
-
- break;
- }
- }
- // if all players are active, the new join gets ignored
- }
- printf( "valid join packet from %s\n", fromPlayerID.c_str() );
-
- if ( numPlayersToJoin == 0 ) {
- std::printf( "Server starting the game!\n" );
-
- // Got the join packet from everyone, start the game!
- setupPacket.startGame = 1;
-
- StartupWithCorrectWads( setupPacket.map.dataset );
-
- StartNetGame();
- ShowGLView();
- }
-
- // Broadcast another setup packet to let each client know that someone else joined.
- SendGameCenterSetup();
-
- return;
- }
-
-
- // The only other packets we should be recieving are client and server packets. This call
- // will handle those.
- iphoneProcessPacket( NULL, data, numBytes );
-
- return;
- }
- void SetupEmptyNetGame() {
- // Disconnect from any previous multiplayer game
- idGameCenter::DisconnectFromMatch();
-
- // 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 = mpExpansion->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;
-
- idGameCenter::SendPacketToPlayerReliable( serverGameCenterID, &pj, sizeof( pj ) );
- }
- /*
- ==================
- SendGameCenterSetup
-
- the server sends out a setup packet to each joined client so they
- can see the game options needed to start the game.
- ==================
- */
- void SendGameCenterSetup() {
- if ( setupPacket.gameID != localGameID ) {
- // we aren't the server
- return;
- }
-
- if ( gametic >= 2 ) {
- // everyone has already started, so they don't need more setup packets
- return;
- }
- setupPacket.sendCount++;
- idGameCenter::BroadcastPacketReliable( &setupPacket, sizeof( setupPacket ) );
-
- }
- //------------------------
- // In order to facilitate automatching through Game Center, we can use a 32-bit playerGroup
- // value. Players will only be matched with other players with the exact same playerGroup.
- // Unfortunately, there doesn't seem to be a way to do more complex logical operations, such
- // as match a player who is willing to play on any map with a player who has chosen a specific
- // map.
- //
- // These functions will take the game setup parameters and create a 32-bit playerGroup value to
- // match with other players who have chosen the same settings.
- //
- // In order to pack the game parameters into 32 bits, we use the following information:
- //
- // Deathmatch field requires 2 bits for 4 possible values.
- // This will be 0 for co-op, 1 for deathmatch, and 2 for altdeath.
- //
- // Expansion pack field requires 3 bits for 8 possible values, but we only have 5 expansions
- // currently.
- // This will simply be the enum value of iphoneMissionPack_t.
- //
- // Map number will require 5 bits for 32 possible maps per expansion, but let's use 6 since a)
- // we have the space b) it allows for future expansion and c) allows us to start counting at 1,
- // which is the natural first index for map nums.
- //
- // The frag limit is capped by us at 20, so we'll use 5 bits for this value.
- // Zero here will indicate an infinite frag limit.
- //
- // The time limit is capped to 20 minutes, so we'll use 5 bits for this value.
- // Zero here will indicate unlimited time.
- //
- // Skill will require 3 bits for 8 possible values, as there are 5 skill levels.
- //
- // In summary, we will need 2 + 3 + 6 + 5 + 5 + 3 = 24 bits to completely specify multiplayer
- // parameters.
- //
- // So, the actual format of the playerGroup is:
- //
- // Bits 0-1: Match type.
- // Bits 2-4: Expansion pack.
- // Bits 5-10: Map number.
- // Bits 11-15: Frag limit.
- // Bits 16-20: Time limit in minutes.
- // Bits 21-23: Skill level.
- //------------------------
- namespace {
- const unsigned int deathmatchGroupNumBits = 2;
- const unsigned int expansionGroupNumBits = 3;
- const unsigned int mapGroupNumBits = 6;
- const unsigned int fragLimitGroupNumBits = 5;
- const unsigned int timeLimitGroupNumBits = 5;
-
- // Currently unused
- //static const unsigned int skillGroupNumBits = 3;
-
- const unsigned int deathmatchGroupOffset = 0;
- const unsigned int expansionGroupOffset = deathmatchGroupOffset + deathmatchGroupNumBits;
- const unsigned int mapGroupOffset = expansionGroupOffset + expansionGroupNumBits;
- const unsigned int fragLimitGroupOffset = mapGroupOffset + mapGroupNumBits;
- const unsigned int timeLimitGroupOffset = fragLimitGroupOffset + fragLimitGroupNumBits;
- const unsigned int skillGroupOffset = timeLimitGroupOffset + timeLimitGroupNumBits;
- }
- //------------------------
- // deathmatch: 1 for deathmatch, 2 for altdeath, 0 for cooperative
- //------------------------
- std::tr1::uint32_t GeneratePlayerGroup( const int deathmatch,
- const int missionPack,
- const int mapNum,
- const int fragLimit,
- const int timeLimit,
- const int skill ) {
-
-
- const int deathmatchGroup = deathmatch << deathmatchGroupOffset;
- const int expansionGroup = static_cast<int>( missionPack ) << expansionGroupOffset;
- const int mapNumGroup = mapNum << mapGroupOffset;
- const int tempFragLimitGroup = fragLimit << fragLimitGroupOffset;
- const int tempTimeLimitGroup = timeLimit << timeLimitGroupOffset;
- const int tempSkillGroup = skill << skillGroupOffset;
-
- // Don't let deathmatch or co-op specific options influence the matchmaking process.
- // If deathmatch, always set skill to 0.
- // If co-op, always set frag limit and time limit to 0.
- const int fragLimitGroup = ( deathmatch == 0 ) ? 0 : tempFragLimitGroup;
- const int timeLimitGroup = ( deathmatch == 0 ) ? 0 : tempTimeLimitGroup;
- const int skillGroup = ( deathmatch != 0 ) ? 0 : tempSkillGroup;
- const int playerGroup = deathmatchGroup
- | expansionGroup
- | mapNumGroup
- | fragLimitGroup
- | timeLimitGroup
- | skillGroup;
-
- return playerGroup;
- }
- //------------------------
- // Converts a playerGroup from Game Center into a setupPacket_t
- //------------------------
- packetSetup_t GenerateSetupPacketFromPlayerGroup( std::tr1::uint32_t playerGroup ) {
- packetSetup_t packet;
-
- packet.packetType = PACKET_VERSION_SETUP;
- packet.gameID = 0;
- packet.startGame = 0;
- packet.sendCount = 0;
-
- packet.map.dataset = 0;
- packet.map.episode = 0;
- packet.map.map = 0;
- packet.map.skill = 0;
-
- packet.deathmatch = 0;
- packet.fraglimit = 0;
- packet.timelimit = 0;
-
- std::fill( packet.playerID, packet.playerID + sizeof( packet.playerID ), 0 );
- return packet;
- }
|