/*********************** qrcsample.c GameSpy Query & Reporting SDK Copyright 1999-2007 GameSpy Industries, Inc devsupport@gamespy.com ****** See the ReadMe file for qrcsample info, and consult the GameSpy Query & Reporting 2 SDK documentation for more information on implementing the qr2 SDK. ************************/ /******** INCLUDES ********/ #include "../qr2.h" /******** DEFINES ********/ // set some of the fixed server keys #define GAME_VERSION _T("2.00") #define GAME_NAME _T("gmtest") #define MAX_PLAYERS 32 #define MAX_TEAMS 2 #define BASE_PORT 11111 // ensure cross-platform compatibility for printf #ifdef _WIN32_WCE void RetailOutputA(CHAR *tszErr, ...); #define printf RetailOutputA #elif defined(_NITRO) #include "../../common/nitro/screen.h" #define printf Printf #define vprintf VPrintf #endif // define our additional keys, making sure not to overwrite the reserved standard key ids // standard keys use 0-NUM_RESERVED_KEYS (defined in qr2regkeys.h) #define GRAVITY_KEY 100 #define RANKINGON_KEY 101 #define TIME__KEY 102 #define AVGPING_T_KEY 103 /******** TYPDEFS ********/ //representative of a game player structure typedef struct { gsi_char pname[80]; int pfrags; int pdeaths; int ptime; int pping; int pteam; } player_t; //representative of a team structure typedef struct { gsi_char tname[80]; int tscore; int avgping; } team_t; //representative of a game data structure typedef struct { player_t players[MAX_PLAYERS]; team_t teams[MAX_TEAMS]; gsi_char mapname[20]; gsi_char hostname[120]; gsi_char gamemode[200]; gsi_char gametype[30]; int numteams; int numplayers; int maxplayers; int fraglimit; int timelimit; int teamplay; int rankingson; int gravity; int hostport; } gamedata_t; /******** GLOBAL VARS ********/ // just to give us bogus data gsi_char *constnames[MAX_PLAYERS]= { _T("Joe Player"), _T("L33t 0n3"), _T("Raptor"), _T("Gr81"), _T("Flubber"), _T("Sarge"), _T("Void"), _T("runaway"), _T("Ph3ar"), _T("wh00t"), _T("gr1nder"),_T("Mace"), _T("stacy"), _T("lamby"), _T("Thrush"), _T("Leeroy") }; gamedata_t gamedata; // to store all the server/player/teamkeys /******** DEBUG OUTPUT ********/ #ifdef GSI_COMMON_DEBUG static void DebugCallback(GSIDebugCategory theCat, GSIDebugType theType, GSIDebugLevel theLevel, const char * theTokenStr, va_list theParamList) { GSI_UNUSED(theLevel); printf("[%s][%s] ", gGSIDebugCatStrings[theCat], gGSIDebugTypeStrings[theType]); vprintf(theTokenStr, theParamList); } #ifdef GSI_UNICODE static void AppDebug(const unsigned short* format, ...) { // Construct text, then pass in as ASCII unsigned short buf[1024]; char tmp[2056]; va_list aList; va_start(aList, format); vswprintf(buf, 1024, format, aList); UCS2ToAsciiString(buf, tmp); gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, "%s", tmp); } #else static void AppDebug(const char* format, ...) { va_list aList; va_start(aList, format); gsDebugVaList(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, format, aList); } #endif #else #define AppDebug _tprintf #endif /******** PROTOTYPES - To prevent warnings on codewarrior strict compile ********/ void serverkey_callback(int keyid, qr2_buffer_t outbuf, void *userdata); void playerkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata); void teamkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata); void keylist_callback(qr2_key_type keytype, qr2_keybuffer_t keybuffer, void *userdata); int count_callback(qr2_key_type keytype, void *userdata); void adderror_callback(qr2_error_t error, gsi_char *errmsg, void *userdata); void nn_callback(int cookie, void *userdata); void cm_callback(gsi_char *data, int len, void *userdata); void cc_callback(SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata); void DoGameStuff(gsi_time totalTime); int test_main(int argc, char **argp); // called when a server key needs to be reported void serverkey_callback(int keyid, qr2_buffer_t outbuf, void *userdata) { AppDebug("Reporting server keys\n"); switch (keyid) { case HOSTNAME_KEY: qr2_buffer_add(outbuf, gamedata.hostname); break; case GAMEVER_KEY: qr2_buffer_add(outbuf, GAME_VERSION); break; case HOSTPORT_KEY: qr2_buffer_add_int(outbuf, gamedata.hostport); break; case MAPNAME_KEY: qr2_buffer_add(outbuf, gamedata.mapname); break; case GAMETYPE_KEY: qr2_buffer_add(outbuf, gamedata.gametype); break; case NUMPLAYERS_KEY: qr2_buffer_add_int(outbuf, gamedata.numplayers); break; case NUMTEAMS_KEY: qr2_buffer_add_int(outbuf, gamedata.numteams); break; case MAXPLAYERS_KEY: qr2_buffer_add_int(outbuf, gamedata.maxplayers); break; case GAMEMODE_KEY: qr2_buffer_add(outbuf, gamedata.gamemode); break; case TEAMPLAY_KEY: qr2_buffer_add_int(outbuf, gamedata.teamplay); break; case FRAGLIMIT_KEY: qr2_buffer_add_int(outbuf, gamedata.fraglimit); break; case TIMELIMIT_KEY: qr2_buffer_add_int(outbuf, gamedata.timelimit); break; case GRAVITY_KEY: qr2_buffer_add_int(outbuf, gamedata.gravity); break; case RANKINGON_KEY: qr2_buffer_add_int(outbuf, gamedata.rankingson); break; default: qr2_buffer_add(outbuf, _T("")); } GSI_UNUSED(userdata); } // called when a player key needs to be reported void playerkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata) { AppDebug("Reporting player keys\n"); //check for valid index if (index >= gamedata.numplayers) { qr2_buffer_add(outbuf, _T("")); return; } switch (keyid) { case PLAYER__KEY: qr2_buffer_add(outbuf, gamedata.players[index].pname); break; case SCORE__KEY: qr2_buffer_add_int(outbuf, gamedata.players[index].pfrags); break; case DEATHS__KEY: qr2_buffer_add_int(outbuf, gamedata.players[index].pdeaths); break; case PING__KEY: qr2_buffer_add_int(outbuf, gamedata.players[index].pping); break; case TEAM__KEY: qr2_buffer_add_int(outbuf, gamedata.players[index].pteam); break; case TIME__KEY: qr2_buffer_add_int(outbuf, gamedata.players[index].ptime); break; default: qr2_buffer_add(outbuf, _T("")); break; } GSI_UNUSED(userdata); } // called when a team key needs to be reported void teamkey_callback(int keyid, int index, qr2_buffer_t outbuf, void *userdata) { AppDebug("Reporting team keys\n"); //check for valid index if (index >= gamedata.numteams) { qr2_buffer_add(outbuf, _T("")); return; } switch (keyid) { case TEAM_T_KEY: qr2_buffer_add(outbuf, gamedata.teams[index].tname); break; case SCORE_T_KEY: qr2_buffer_add_int(outbuf, gamedata.teams[index].tscore); break; case AVGPING_T_KEY: qr2_buffer_add_int(outbuf, gamedata.teams[index].avgping); break; default: qr2_buffer_add(outbuf, _T("")); break; } GSI_UNUSED(userdata); } // called when we need to report the list of keys we report values for void keylist_callback(qr2_key_type keytype, qr2_keybuffer_t keybuffer, void *userdata) { AppDebug("Reporting keylist\n"); //need to add all the keys we support switch (keytype) { case key_server: qr2_keybuffer_add(keybuffer, HOSTNAME_KEY); qr2_keybuffer_add(keybuffer, GAMEVER_KEY); qr2_keybuffer_add(keybuffer, HOSTPORT_KEY); qr2_keybuffer_add(keybuffer, MAPNAME_KEY); qr2_keybuffer_add(keybuffer, GAMETYPE_KEY); qr2_keybuffer_add(keybuffer, NUMPLAYERS_KEY); qr2_keybuffer_add(keybuffer, NUMTEAMS_KEY); qr2_keybuffer_add(keybuffer, MAXPLAYERS_KEY); qr2_keybuffer_add(keybuffer, GAMEMODE_KEY); qr2_keybuffer_add(keybuffer, TEAMPLAY_KEY); qr2_keybuffer_add(keybuffer, FRAGLIMIT_KEY); qr2_keybuffer_add(keybuffer, TIMELIMIT_KEY); qr2_keybuffer_add(keybuffer, GRAVITY_KEY); //a custom key qr2_keybuffer_add(keybuffer, RANKINGON_KEY); //a custom key break; case key_player: qr2_keybuffer_add(keybuffer, PLAYER__KEY); qr2_keybuffer_add(keybuffer, SCORE__KEY); qr2_keybuffer_add(keybuffer, DEATHS__KEY); qr2_keybuffer_add(keybuffer, PING__KEY); qr2_keybuffer_add(keybuffer, TEAM__KEY); qr2_keybuffer_add(keybuffer, TIME__KEY); //a custom key break; case key_team: qr2_keybuffer_add(keybuffer, TEAM_T_KEY); qr2_keybuffer_add(keybuffer, SCORE_T_KEY); qr2_keybuffer_add(keybuffer, AVGPING_T_KEY); //a custom key break; default: break; } GSI_UNUSED(userdata); } // called when we need to report the number of players and teams int count_callback(qr2_key_type keytype, void *userdata) { AppDebug("Reporting number of players/teams\n"); if (keytype == key_player) return gamedata.numplayers; else if (keytype == key_team) return gamedata.numteams; else return 0; GSI_UNUSED(userdata); } // called if our registration with the GameSpy master server failed void adderror_callback(qr2_error_t error, gsi_char *errmsg, void *userdata) { GS_ASSERT(errmsg) AppDebug("Error adding server: %d, %s\n", error, errmsg); GSI_UNUSED(userdata); } // called when a client wants to connect using nat negotiation // (Nat Negotiation must be enabled in qr2_init) void nn_callback(int cookie, void *userdata) { AppDebug("Got natneg cookie: %d\n", cookie); GSI_UNUSED(userdata); } // called when a client sends a message to the server through qr2 (not commonly used) void cm_callback(gsi_char *data, int len, void *userdata) { AppDebug("Got %d bytes from client\n", len); GSI_UNUSED(data); GSI_UNUSED(userdata); } // called when a client has connected void cc_callback(SOCKET gamesocket, struct sockaddr_in *remoteaddr, void *userdata) { AppDebug("Client connected from %s:%d\n", inet_ntoa(remoteaddr->sin_addr), ntohs(remoteaddr->sin_port)); GSI_UNUSED(gamesocket); GSI_UNUSED(userdata); } // initialize the gamedata structure with bogus data static void init_game(void) { int i; AppDebug("Generating game data\n"); srand((unsigned int) current_time() ); gamedata.numplayers = rand() % 15 + 1; gamedata.maxplayers = MAX_PLAYERS; for (i = 0 ; i < gamedata.numplayers ; i++) { _tcscpy(gamedata.players[i].pname, constnames[i]); gamedata.players[i].pfrags = rand() % 32; gamedata.players[i].pdeaths = rand() % 32; gamedata.players[i].ptime = rand() % 1000; gamedata.players[i].pping = rand() % 500; gamedata.players[i].pteam = rand() % 2; } gamedata.numteams = 2; for (i = 0 ; i < gamedata.numteams ; i++) { gamedata.teams[i].tscore = rand() % 500; gamedata.teams[i].avgping = rand() % 500; } _tcscpy(gamedata.teams[0].tname,_T("Red")); _tcscpy(gamedata.teams[1].tname,_T("Blue")); _tcscpy(gamedata.mapname,_T("gmtmap1")); _tcscpy(gamedata.gametype,_T("arena")); _tcscpy(gamedata.hostname,_T("GameSpy QR2 Sample")); _tcscpy(gamedata.gamemode,_T("openplaying")); gamedata.fraglimit = 0; gamedata.timelimit = 40; gamedata.teamplay = 1; gamedata.rankingson = 1; gamedata.gravity = 800; gamedata.hostport = 25000; } // simulate whatever else a game server does void DoGameStuff(gsi_time totalTime) { // After 30 seconds, we will change the game map and call qr2_send_statechanged // This should only be called after major changes such as mapname or gametype, and // cannot be applied more than once every 10 seconds (subsequent calls will be delayed // when necessary) static int stateChanged = 0; if (!stateChanged && totalTime > 30000) { AppDebug("Mapname changed, calling qr2_send_statechanged\n"); _tcscpy(gamedata.mapname,_T("gmtmap2")); qr2_send_statechanged(NULL); stateChanged = 1; } msleep(10); } int test_main(int argc, char **argp) { /* qr2_init parameters */ gsi_char secret_key[9]; // your title's assigned secret key gsi_char ip[255]; // to manually set local IP const int isPublic = 1; // set to '0' for a LAN game const int isNatNegSupported = 1; // set to '0' if you don't support Nat Negotiation gsi_time aStartTime = 0; // for sample, so we don't run forever void * userData = NULL; // optional data that will be passed to the callback functions // for debug output on these platforms #if defined (_PS3) || defined (_PS2) || defined (_PSP) || defined(_NITRO) #ifdef GSI_COMMON_DEBUG // Define GSI_COMMON_DEBUG if you want to view the SDK debug output // Set the SDK debug log file, or set your own handler using gsSetDebugCallback //gsSetDebugFile(stdout); // output to console gsSetDebugCallback(DebugCallback); // Set debug levels gsSetDebugLevel(GSIDebugCat_All, GSIDebugType_All, GSIDebugLevel_Verbose); #endif #endif //set the secret key, in a semi-obfuscated manner secret_key[0] = 'H'; secret_key[1] = 'A'; secret_key[2] = '6'; secret_key[3] = 'z'; secret_key[4] = 'k'; secret_key[5] = 'S'; secret_key[6] = '\0'; // register our custom keys (you do not have to register the reserved standard keys) AppDebug("Registering custom keys\n"); qr2_register_key(GRAVITY_KEY, _T("gravity") ); qr2_register_key(RANKINGON_KEY, _T("rankingon")); qr2_register_key(TIME__KEY, _T("time_") ); // player keys always end with '_' qr2_register_key(AVGPING_T_KEY, _T("avgping_t")); // team keys always end with '_t' // create some random game data init_game(); // Check if we want to override our IP (otherwise qr2 will set for us) #ifndef GSI_UNICODE if (argc>1) strcpy(ip, argp[1]); #else if (argc>1) AsciiToUCS2String(argp[1], ip); #endif AppDebug("Initializing SDK; server should show up on the master list within 6-10 sec.\n"); //Call qr_init with the query port number and gamename, default IP address, and no user data //Pass NULL for the qrec parameter (first parameter) as long as you're running a single game //server instance per process //Reference gt2nat sample for qr2_init_socket implementation if (qr2_init(NULL,argc>1?ip:NULL,BASE_PORT,GAME_NAME, secret_key, isPublic, isNatNegSupported, serverkey_callback, playerkey_callback, teamkey_callback, keylist_callback, count_callback, adderror_callback, userData) != e_qrnoerror) { printf("Error starting query sockets\n"); return -1; } // Set a function to be called when we receive a game specific message qr2_register_clientmessage_callback(NULL, cm_callback); // Set a function to be called when we receive a nat negotiation request qr2_register_natneg_callback(NULL, nn_callback); // Set a function to be called when a client has connected qr2_register_clientconnected_callback(NULL, cc_callback); // Enter the main loop AppDebug("Sample will quit after 60 seconds\n"); aStartTime = current_time(); while ((current_time() - aStartTime) < 60000) { gsi_time totalTime = current_time() - aStartTime; // used to change the game state after 30 seconds // An actual game would do something between "thinks" DoGameStuff(totalTime); //check for / process incoming queries //should be called every 10-100 ms; quicker calls produce more accurate ping measurements qr2_think(NULL); } AppDebug("Shutting down - server will be removed from the master server list\n"); //let gamemaster know we are shutting down (removes dead server entry from the list) qr2_shutdown(NULL); #ifdef GSI_UNICODE // In Unicode mode we must perform additional cleanup qr2_internal_key_list_free(); #endif // Finished return 0; }