DoomGameCenterMatch.cpp 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. /*
  2. Copyright (C) 2009-2011 id Software LLC, a ZeniMax Media company.
  3. This program is free software; you can redistribute it and/or
  4. modify it under the terms of the GNU General Public License
  5. as published by the Free Software Foundation; either version 2
  6. of the License, or (at your option) any later version.
  7. This program is distributed in the hope that it will be useful,
  8. but WITHOUT ANY WARRANTY; without even the implied warranty of
  9. MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  10. GNU General Public License for more details.
  11. You should have received a copy of the GNU General Public License
  12. along with this program; if not, write to the Free Software
  13. Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
  14. */
  15. #include "DoomGameCenterMatch.h"
  16. #include "doomiphone.h"
  17. #include "ios/ios_interface.h"
  18. #include <cstdio>
  19. #include <string>
  20. #include <algorithm>
  21. DoomGameCenterMatch gDoomGameCenterMatch;
  22. std::string serverGameCenterID;
  23. std::tr1::array<std::string, 4> playerIndexToIDMap;
  24. namespace {
  25. bool IsServer() {
  26. return setupPacket.gameID == localGameID;
  27. }
  28. int numPlayersToJoin = 0;
  29. }
  30. DoomGameCenterMatch::~DoomGameCenterMatch() {
  31. }
  32. void DoomGameCenterMatch::createdMatchImpl() {
  33. std::printf( "Created a DOOM match!\n" );
  34. }
  35. void DoomGameCenterMatch::allPlayersConnectedImpl( std::vector<std::string> connectedPlayerIDs ) {
  36. std::printf( "All players connected to a DOOM match!\n" );
  37. // Send an initial setup packet if this is the server, this will cause clients to
  38. // send their join packets.
  39. if ( IsServer() ) {
  40. printf( "Server broadcasting initial setup packet.\n" );
  41. numPlayersToJoin = connectedPlayerIDs.size();
  42. assert( numPlayersToJoin > 0 );
  43. SendGameCenterSetup();
  44. }
  45. }
  46. void DoomGameCenterMatch::playerConnectedImpl( std::string playerIdentifier ) {
  47. std::printf( "Player %s connected!\n", playerIdentifier.c_str() );
  48. }
  49. void DoomGameCenterMatch::playerDisconnectedImpl( std::string playerIdentifier ) {
  50. std::printf( "Player %s disconnected!\n", playerIdentifier.c_str() );
  51. if ( IsServer() ) {
  52. // Only the server tracks disconnected players. The next server packet sent to each
  53. // client will have the new playeringame[MAXPLAYERS] info.
  54. for ( int i = 0; i < MAXPLAYERS; ++i ) {
  55. if ( playerIndexToIDMap[i] == playerIdentifier ) {
  56. playeringame[i] = false;
  57. }
  58. }
  59. } else {
  60. // If we are a client, and the server is the one that disconnected, we're hosed.
  61. if ( !netGameFailure && playerIdentifier == serverGameCenterID ) {
  62. netGameFailure = NF_LOST_SERVER;
  63. idGameCenter::DisconnectFromMatch();
  64. ShowSystemAlert( "#LostServerTitle", "#LostServerMessage" );
  65. iphoneMainMenu();
  66. }
  67. }
  68. }
  69. template<class Type, size_t Size>
  70. const Type * end( const Type (&testArray)[Size] ) {
  71. return testArray + Size;
  72. }
  73. template<class Type, size_t Size>
  74. bool ArrayContains( const Type (&testArray)[Size], const Type & value ) {
  75. return std::find( testArray, end( testArray ), value ) != end( testArray );
  76. }
  77. /*
  78. ==================
  79. DoomGameCenterMatch::receivedDataImpl
  80. A packet has been received from Game Center. Because the Game Center "callback" actually runs
  81. on the main thread, it should be safe to directly process the packets here. This should only
  82. be called in between displaylink updates.
  83. ==================
  84. */
  85. void DoomGameCenterMatch::receivedDataImpl( std::string fromPlayerID, const void * data, int numBytes ) {
  86. if ( numBytes < 4 ) {
  87. std::printf( "discarding packet because numBytes = %i.\n", numBytes );
  88. return;
  89. }
  90. int packetID = *(int *)data;
  91. if ( packetID == PACKET_VERSION_SETUP ) {
  92. std::printf( "Received a setup packet!\n" );
  93. // Only the server sends setup packets, and we need to keep track of it's ID.
  94. serverGameCenterID = fromPlayerID;
  95. if ( localGameID == setupPacket.gameID ) {
  96. // if we are sending packets, always ignore other setup packets
  97. printf( "discarding setup packet because we are the server\n" );
  98. return;
  99. }
  100. setupPacketFrameNum = iphoneFrameNum;
  101. // save this packet
  102. setupPacket = *(packetSetup_t *)data;
  103. // Send a join packet to the server so that it's aware of this client, if this client
  104. // isn't already in the list.
  105. if ( !ArrayContains( setupPacket.playerID, localGameID ) ) {
  106. printf( "This client has not notified the server yet - sending join packet.\n" );
  107. SendJoinPacket();
  108. }
  109. // check for game start in a received setup packet
  110. if ( !netgame && setupPacket.startGame ) {
  111. StartupWithCorrectWads( setupPacket.map.dataset );
  112. ShowGLView();
  113. if ( StartNetGame() ) {
  114. setupPacket.startGame = false;
  115. // we aren't in this game
  116. }
  117. }
  118. return;
  119. }
  120. if ( packetID == PACKET_VERSION_JOIN ) {
  121. // we should only process join packets if we are running the current game
  122. if ( setupPacket.gameID != localGameID ) {
  123. printf( "discarding join packet because we aren't the server\n" );
  124. return;
  125. }
  126. packetJoin_t *pj = (packetJoin_t *)data;
  127. if ( pj->playerID == 0 ) {
  128. // should never happen
  129. printf( "discarding join packet because playerID is 0\n" );
  130. return;
  131. }
  132. // add this player
  133. int i;
  134. for ( i = 0 ; i < MAXPLAYERS ; i++ ) {
  135. if ( setupPacket.playerID[i] == pj->playerID ) {
  136. netPlayers[i].peer.lastPacketTime = SysIphoneMilliseconds();
  137. break;
  138. }
  139. }
  140. if ( i == MAXPLAYERS ) {
  141. // not in yet, add if possible
  142. for ( i = 0 ; i < MAXPLAYERS ; i++ ) {
  143. if ( setupPacket.playerID[i] == 0 ) {
  144. setupPacket.playerID[i] = pj->playerID;
  145. // Save this client's GameCenter ID to send it packets later.
  146. playerIndexToIDMap[i] = fromPlayerID;
  147. //netPlayers[i].peer.address = *from;
  148. netPlayers[i].peer.lastPacketTime = SysIphoneMilliseconds();
  149. --numPlayersToJoin;
  150. break;
  151. }
  152. }
  153. // if all players are active, the new join gets ignored
  154. }
  155. printf( "valid join packet from %s\n", fromPlayerID.c_str() );
  156. if ( numPlayersToJoin == 0 ) {
  157. std::printf( "Server starting the game!\n" );
  158. // Got the join packet from everyone, start the game!
  159. setupPacket.startGame = 1;
  160. StartupWithCorrectWads( setupPacket.map.dataset );
  161. StartNetGame();
  162. ShowGLView();
  163. }
  164. // Broadcast another setup packet to let each client know that someone else joined.
  165. SendGameCenterSetup();
  166. return;
  167. }
  168. // The only other packets we should be recieving are client and server packets. This call
  169. // will handle those.
  170. iphoneProcessPacket( NULL, data, numBytes );
  171. return;
  172. }
  173. void SetupEmptyNetGame() {
  174. // Disconnect from any previous multiplayer game
  175. idGameCenter::DisconnectFromMatch();
  176. // no current setup packet, so initialize with this phone's default values
  177. localGameID = SysIphoneMicroseconds();
  178. memset( &setupPacket, 0, sizeof( setupPacket ) );
  179. setupPacket.gameID = localGameID;
  180. setupPacket.packetType = PACKET_VERSION_SETUP;
  181. setupPacket.map.dataset = mpExpansion->value;
  182. setupPacket.map.episode = mpEpisode->value;
  183. setupPacket.map.map = mpMap->value;
  184. setupPacket.map.skill = mpSkill->value;
  185. setupPacket.deathmatch = mpDeathmatch->value;
  186. setupPacket.timelimit = timeLimit->value;
  187. setupPacket.fraglimit = fragLimit->value;
  188. setupPacket.playerID[0] = playerID;
  189. }
  190. /*
  191. ==================
  192. SendJoinPacket
  193. These will be sent to the server ever frame we are in the multiplayer menu.
  194. ==================
  195. */
  196. void SendJoinPacket() {
  197. packetJoin_t pj;
  198. pj.packetType = PACKET_VERSION_JOIN;
  199. pj.gameID = setupPacket.gameID;
  200. pj.playerID = playerID;
  201. idGameCenter::SendPacketToPlayerReliable( serverGameCenterID, &pj, sizeof( pj ) );
  202. }
  203. /*
  204. ==================
  205. SendGameCenterSetup
  206. the server sends out a setup packet to each joined client so they
  207. can see the game options needed to start the game.
  208. ==================
  209. */
  210. void SendGameCenterSetup() {
  211. if ( setupPacket.gameID != localGameID ) {
  212. // we aren't the server
  213. return;
  214. }
  215. if ( gametic >= 2 ) {
  216. // everyone has already started, so they don't need more setup packets
  217. return;
  218. }
  219. setupPacket.sendCount++;
  220. idGameCenter::BroadcastPacketReliable( &setupPacket, sizeof( setupPacket ) );
  221. }
  222. //------------------------
  223. // In order to facilitate automatching through Game Center, we can use a 32-bit playerGroup
  224. // value. Players will only be matched with other players with the exact same playerGroup.
  225. // Unfortunately, there doesn't seem to be a way to do more complex logical operations, such
  226. // as match a player who is willing to play on any map with a player who has chosen a specific
  227. // map.
  228. //
  229. // These functions will take the game setup parameters and create a 32-bit playerGroup value to
  230. // match with other players who have chosen the same settings.
  231. //
  232. // In order to pack the game parameters into 32 bits, we use the following information:
  233. //
  234. // Deathmatch field requires 2 bits for 4 possible values.
  235. // This will be 0 for co-op, 1 for deathmatch, and 2 for altdeath.
  236. //
  237. // Expansion pack field requires 3 bits for 8 possible values, but we only have 5 expansions
  238. // currently.
  239. // This will simply be the enum value of iphoneMissionPack_t.
  240. //
  241. // Map number will require 5 bits for 32 possible maps per expansion, but let's use 6 since a)
  242. // we have the space b) it allows for future expansion and c) allows us to start counting at 1,
  243. // which is the natural first index for map nums.
  244. //
  245. // The frag limit is capped by us at 20, so we'll use 5 bits for this value.
  246. // Zero here will indicate an infinite frag limit.
  247. //
  248. // The time limit is capped to 20 minutes, so we'll use 5 bits for this value.
  249. // Zero here will indicate unlimited time.
  250. //
  251. // Skill will require 3 bits for 8 possible values, as there are 5 skill levels.
  252. //
  253. // In summary, we will need 2 + 3 + 6 + 5 + 5 + 3 = 24 bits to completely specify multiplayer
  254. // parameters.
  255. //
  256. // So, the actual format of the playerGroup is:
  257. //
  258. // Bits 0-1: Match type.
  259. // Bits 2-4: Expansion pack.
  260. // Bits 5-10: Map number.
  261. // Bits 11-15: Frag limit.
  262. // Bits 16-20: Time limit in minutes.
  263. // Bits 21-23: Skill level.
  264. //------------------------
  265. namespace {
  266. const unsigned int deathmatchGroupNumBits = 2;
  267. const unsigned int expansionGroupNumBits = 3;
  268. const unsigned int mapGroupNumBits = 6;
  269. const unsigned int fragLimitGroupNumBits = 5;
  270. const unsigned int timeLimitGroupNumBits = 5;
  271. // Currently unused
  272. //static const unsigned int skillGroupNumBits = 3;
  273. const unsigned int deathmatchGroupOffset = 0;
  274. const unsigned int expansionGroupOffset = deathmatchGroupOffset + deathmatchGroupNumBits;
  275. const unsigned int mapGroupOffset = expansionGroupOffset + expansionGroupNumBits;
  276. const unsigned int fragLimitGroupOffset = mapGroupOffset + mapGroupNumBits;
  277. const unsigned int timeLimitGroupOffset = fragLimitGroupOffset + fragLimitGroupNumBits;
  278. const unsigned int skillGroupOffset = timeLimitGroupOffset + timeLimitGroupNumBits;
  279. }
  280. //------------------------
  281. // deathmatch: 1 for deathmatch, 2 for altdeath, 0 for cooperative
  282. //------------------------
  283. std::tr1::uint32_t GeneratePlayerGroup( const int deathmatch,
  284. const int missionPack,
  285. const int mapNum,
  286. const int fragLimit,
  287. const int timeLimit,
  288. const int skill ) {
  289. const int deathmatchGroup = deathmatch << deathmatchGroupOffset;
  290. const int expansionGroup = static_cast<int>( missionPack ) << expansionGroupOffset;
  291. const int mapNumGroup = mapNum << mapGroupOffset;
  292. const int tempFragLimitGroup = fragLimit << fragLimitGroupOffset;
  293. const int tempTimeLimitGroup = timeLimit << timeLimitGroupOffset;
  294. const int tempSkillGroup = skill << skillGroupOffset;
  295. // Don't let deathmatch or co-op specific options influence the matchmaking process.
  296. // If deathmatch, always set skill to 0.
  297. // If co-op, always set frag limit and time limit to 0.
  298. const int fragLimitGroup = ( deathmatch == 0 ) ? 0 : tempFragLimitGroup;
  299. const int timeLimitGroup = ( deathmatch == 0 ) ? 0 : tempTimeLimitGroup;
  300. const int skillGroup = ( deathmatch != 0 ) ? 0 : tempSkillGroup;
  301. const int playerGroup = deathmatchGroup
  302. | expansionGroup
  303. | mapNumGroup
  304. | fragLimitGroup
  305. | timeLimitGroup
  306. | skillGroup;
  307. return playerGroup;
  308. }
  309. //------------------------
  310. // Converts a playerGroup from Game Center into a setupPacket_t
  311. //------------------------
  312. packetSetup_t GenerateSetupPacketFromPlayerGroup( std::tr1::uint32_t playerGroup ) {
  313. packetSetup_t packet;
  314. packet.packetType = PACKET_VERSION_SETUP;
  315. packet.gameID = 0;
  316. packet.startGame = 0;
  317. packet.sendCount = 0;
  318. packet.map.dataset = 0;
  319. packet.map.episode = 0;
  320. packet.map.map = 0;
  321. packet.map.skill = 0;
  322. packet.deathmatch = 0;
  323. packet.fraglimit = 0;
  324. packet.timelimit = 0;
  325. std::fill( packet.playerID, packet.playerID + sizeof( packet.playerID ), 0 );
  326. return packet;
  327. }