diff --git a/Samples/TapRace/iphone/AppDelegate.h b/Samples/TapRace/iphone/AppDelegate.h new file mode 100644 index 00000000..45df6dd1 --- /dev/null +++ b/Samples/TapRace/iphone/AppDelegate.h @@ -0,0 +1,268 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import +#import +#import + +#import "gt2/gt2.h" +#import "sc/sc.h" +#import "gp/gp.h" +#import "serverbrowsing/sb_serverbrowsing.h" +#import "sake/sake.h" + +/////////////////////////////////////////////////////////////////////////////// +/////////////////////////////////////////////////////////////////////////////// +// global defines used in the sample + +#define SCRACE_GAMENAME "taprace" +#define SCRACE_SECRETKEY "xJRENu" +#define SCRACE_GAMEID 2431 +#define SCRACE_PRODUCTID 11675 +#define SCRACE_VERSION 1.3 +#define SCRACE_VERSION_STR "1.3" + + +#define LEADERBOARD_INVALID 0 +#define LEADERBOARD_BY_TIME 1 +#define LEADERBOARD_BY_BUDDIES 2 +#define LEADERBOARD_BY_LOCATION 3 + +#define MAX_BUDDIES 100 +#define MAX_LEADERS 25 +#define MAX_MAPPOINTS 100 +#define MAX_SERVERS 20 +#define TAP_POLL_TIME 0.01 // seconds +#define INITIAL_BEST_TIME 99999 // milliseconds + +#define THUMBNAIL_WIDTH 96 +#define THUMBNAIL_HEIGHT 96 +#define FULLSIZE_IMAGE_WIDTH 320 +#define FULLSIZE_IMAGE_HEIGHT 320 + +#define PLAYER1 0 +#define PLAYER2 1 + +// define for debugging.... +#undef USE_DUMMY_LEADERBOARD_DATA + +@class AppDelegate; +@class GameController; +@class LeaderboardObject; + +typedef struct PlayerData PlayerData; + +extern char raceDateTimeString[256]; +extern PlayerData gPlayerData; +extern PlayerData gOpponentData; +//extern LeaderboardObject gOpponentData; // current opponent +extern int gLeaderboardType; // type of leaderboard data +extern int gLeaderboardProfileIndex; // index of currently displayed profile +extern NSMutableArray* gLeaderboardData; // most recent leaderboard download (array of LeaderboardObject's) +extern AppDelegate* appDelegate; +extern bool sdkInitialized; +extern bool lostConnection; // true when we lose network connectivity +extern bool userKickedOut; // set to true if same user logs into another device + + +@interface AppDelegate : NSObject +{ + IBOutlet UIWindow* window; + IBOutlet UINavigationController* navController; + + NSTimer* loadRemoteDataTimer; + NSTimer* thinkTimer; + NSTimer* countdownTimer; + NSTimer* hostingCountdownTimer; + NSTimer* joiningCountdownTimer; + UIAlertView* alertView; + + GameController* gameController; + + GT2Socket gt2Socket; + GT2Connection gt2Connection; + SCInterfacePtr interface; + ServerBrowser serverBrowser; + SAKE sake; + + GSLoginCertificate remoteCertificate; + gsi_u8 remoteConnId[SC_CONNECTION_GUID_SIZE]; + SCReportPtr statsReport; + + uint64_t remoteTime; + uint64_t localTime; + uint64_t start; + + int state; + int currentAlert; + int playerCount; + int countdown; + int hostingCountdown; + int joiningCountdown; + int natnegCookie; + bool connected; // host connected to client + bool fromMenu; + + bool hosting; + bool racing; + bool win; + bool tie; + bool disconnected; + bool reportSent; + bool sessionCreated; + bool connIdSet; + +} + +@property(nonatomic) ServerBrowser serverBrowser; +@property(nonatomic, readonly) SAKE sake; +@property bool connected; +@property(nonatomic, readonly) int state; +@property bool fromMenu; +@property(nonatomic, readonly) uint64_t start; +@property(nonatomic, readonly) uint64_t localTime; +@property(nonatomic, readonly) uint64_t remoteTime; + +- (void)startThinkTimer; +- (void)stopThinkTimer; +- (void)delayedNetworkConnectionErrorMessage; + +- (void)loggedIn; +- (void)kickedOut; +- (bool)isUserKickedOut; +- (void)logout; +- (BOOL)isNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags; + +- (bool)joinGame: (unsigned int)ip port: (unsigned short)port useNatNeg: (bool)useNatNeg; +- (bool)hostGame; + + +- (void)stopHostingServer; +- (void)initGame; +- (void)initSake; +- (void)findGame; +- (void)startSinglePlayerGame; +- (void)startMultiPlayerGame: (const char*)remoteAddress; +- (void)startCountdown; +- (void)startHostingCountdownTimer; +- (void)stopHostingCountdownTimer; +- (void)startJoiningCountdownTimer; +- (void)stopJoiningCountdownTimer; +- (void)restartGame; +- (void)finishedGame; +- (void)returnToMenu; +- (void)returnToMatchMaking; +- (bool)isTwoPlayerGame; +- (void)savePlayerStatsToFile; + +- (void)showAboutInfo: (id) sender; +- (void)flipsideViewControllerDidFinish:(id) sender; +- (void)showLeaderboards; +- (void)showLeadersByTime; +- (void)showTop5LeadersByTime; +- (void)showLeadersByBuddies; +- (void)showLeadersByLocation; +- (void)showUser: (int)index; +- (void)deleteLeaderboards; +- (void)deleteBlockedImagesData; +- (void)removeFalseLeaders; +- (bool)imageIsBlocked: (int) pictureId; +- (void)getMyLeaderPosition; + +- (UIViewController*)findExistingControllerOfType: (Class)type; +- (UIViewController*)pushNewControllerOfType: (Class)type nib: (NSString*)nibName; + +@end + +//*** indicates is used on player stats screens +struct PlayerData +{ + // "Normal" game data + NSString* uniquenick; // player name displayed + gsi_u32 profileId; // player ID + gsi_u32 pictureFileId; // picture fileId + int rank; // player rank + UIImage *thumbNail; // picture of player (not from comrade) + UIImage *fullsizeImage; // bigger picture of player (not from comrade) + CLLocation *playerLocation; // lat/long of player, used for map view + + // login/connection info + char loginTicket[GP_LOGIN_TICKET_LEN]; + GSLoginCertificate certificate; + GSLoginPrivateData privateData; + SCPeerCipher peerSendCipher; // for fast encryption + SCPeerCipher peerRecvCipher; // for fast decryption + + // local data + NSMutableDictionary* playerStatsData; // Player stats data (stored locally) + NSString* playerDataPath; // Player data directory path on device + NSMutableArray* blockedImagesData; // player's blocked image list data (stored locally) + + // Stats connection data (stored remotely) + SCInterfacePtr statsInterface; + gsi_u8 sessionId[SC_SESSION_GUID_SIZE]; + gsi_u8 connectionId[SC_CONNECTION_GUID_SIZE]; + gsi_u8 statsAuthdata[16]; + + // 2 player game stats + int careerWins; // # of 2 player won + int careerLosses; // # of 2 player matches lost + int bestRaceTime; // Player's best 2 player matches race time (milliseconds). + int worstRaceTime; // Player's 2 player matches worst race time (milliseconds). + int totalMatches; // # of 2 player matches played + float averageRaceTime; // Player's average race time per match (milliseconds/match). + int matchWinStreak; // # of consecutive 2 player games won + int matchLossStreak; // # of consecutive 2 player games lost + int totalRaceTime; // Player's total race time for all matches (milliseconds). + int careerDisconnects; // Player's total number of times disconnected. + float disconnectRate; // Player's disconnect rate (disconnects/matches). + int careerDraws; // Player's total number of draws (tied matches). + int matchDrawStreak; // # of consecutive 2 player games ending with draw + int careerLongestWinStreak; // longest consecutive 2 player games won + int careerLongestLossStreak; // longest consecutive 2 player games lost + int careerLongestDrawStreak; // longest consecutive 2 player games ending with draw + int totalCompleteMatches; // Total number of matches where the game went to completion (all win/loss/draw results). + + // single player game stats + int spBestRaceTime; //*** Player's career best race time (milliseconds). + int spWorstRaceTime; // Player's career worst race time (milliseconds). + int spTotalPlays; // total single player games played + float spAverageRaceTime; // Player's average race time per play (milliseconds/match). + int spTotalRaceTime; // Player's total race time for all plays (milliseconds). + + // single and 2 player game stats + char* lastGameTime; // date/time string of last game stats retrieved from Sake + +}; + + +@interface LeaderboardObject : NSObject +{ + NSString* name; + gsi_u32 profileId; + + gsi_u32 pictureFileId; + UIImage *thumbNail; + + int rank; + int careerWins; //*** + int careerLosses; //*** + int bestRaceTime; //*** Player's career best race time (milliseconds). + int totalMatches; //*** + int currentWinStreak; //*** + //*** + CLLocation *playerLocation; // lat/long of player, used for map view +} +@property (nonatomic, copy) NSString * name; +@property gsi_u32 profileId; +@property gsi_u32 pictureFileId; +@property (nonatomic, retain) UIImage * thumbNail; +@property int rank; +@property int careerWins; +@property int careerLosses; +@property int bestRaceTime; +@property int totalMatches; +@property int currentWinStreak; +@property (nonatomic, retain) CLLocation * playerLocation; + +@end diff --git a/Samples/TapRace/iphone/AppDelegate.mm b/Samples/TapRace/iphone/AppDelegate.mm new file mode 100644 index 00000000..fa7cdae5 --- /dev/null +++ b/Samples/TapRace/iphone/AppDelegate.mm @@ -0,0 +1,4126 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "AppDelegate.h" +#import "Utility.h" +#include "atlas_taprace_v2.h" + +#import "LoginController.h" +#import "MenuController.h" +#import "MatchmakingController.h" +#import "GameController.h" +#import "GameResultsController.h" +#import "LeaderboardsController.h" +#import "LeadersController.h" +#import "LeadersByLocationController.h" +#import "UserStatsController.h" + +#import "qr2/qr2.h" +#import "natneg/natneg.h" +#import "gt2/gt2Encode.h" + +#import +#import +#import + + +// Pre-game messaging +#define MSG_CONNECT "is" // client-pid, nick +#define MSG_TO_HOST_CERT "ir" // host-pid, certificate +#define MSG_TO_HOST_CERT_TYPE 1 +#define MSG_TO_CLIENT_CERT "ir" // join-pid, certificate +#define MSG_TO_CLIENT_CERT_TYPE 2 +#define MSG_TO_HOST_CERTVERIFIED "" +#define MSG_TO_HOST_CERTVERIFIED_TYPE 3 // ready for keys +#define MSG_TO_CLIENT_CERTVERIFIED "" +#define MSG_TO_CLIENT_CERTVERIFIED_TYPE 4 // ready for keys +#define MSG_TO_HOST_KEYS "r" // encryption keys +#define MSG_TO_HOST_KEYS_TYPE 5 +#define MSG_TO_CLIENT_KEYS "r" // encrpytion keys +#define MSG_TO_CLIENT_KEYS_TYPE 6 +#define MSG_REPLAY "" +#define MSG_REPLAY_TYPE 7 // replay game +#define MSG_REPLAY_CANCELED "" +#define MSG_REPLAY_CANCELED_TYPE 8 // ...or not + +// Game messaging +#define MSG_COUNTDOWN "i" // count +#define MSG_COUNTDOWN_TYPE 20 +#define MSG_SESSION_ID "rr" // Host session ID, connID exchange +#define MSG_SESSION_ID_TYPE 21 +#define MSG_CONNECTION_ID "r" // connection ID exhcnage +#define MSG_CONNECTION_ID_TYPE 22 +#define MSG_START_RACE "" // race start +#define MSG_START_RACE_TYPE 23 +#define MSG_PROGRESS "i" // progress +#define MSG_PROGRESS_TYPE 24 +#define MSG_END_RACE "i" // time +#define MSG_END_RACE_TYPE 25 +#define MSG_CHAT "s" // message +#define MSG_CHAT_TYPE 26 + +#undef HOST_PORT // previously defined in mach headers +#define HOST_PORT 38466 +#define HOST_PORT2 38468 +#define CLIENT_PORT 38467 +#define HOST_PORT_STRING ":" STRINGIZE(HOST_PORT) +#define CLIENT_PORT_STRING ":" STRINGIZE(CLIENT_PORT) // so you can run both +#define COUNTDOWN_START 5 + +// Stats & SDK constants +#define SCRACE_TIMEOUT_MS 0 +#define SCRACE_SLEEP_MS 100 +#define SCRACE_AUTHORITATIVE gsi_true +#define SCRACE_COLLABORATIVE gsi_false +#define SCRACE_NUM_PLAYERS 2 +#define SCRACE_NUM_TEAMS 2 + +#define SCRACE_HOST_TEAM 7564 // fake team ids +#define SCRACE_CLIENT_TEAM 7565 // fake team ids + +// NAT negotiation +#define NN_SERVER 0 +#define NN_CLIENT 1 + +#define buttonExists( viewButtonIndex ) viewButtonIndex >= 0 + +enum AppState +{ + LOGGED_OUT, + SETTING_UP, + RACING, + FINISHED_RACE, + + HOST_LISTENING, + HOST_CONNECTED, + HOST_ERROR, + HOST_EXCHANGE_CERT, + HOST_VERIFY_CERT, + HOST_EXCHANGE_KEYS, + HOST_WAITING, + HOST_SEND_SESSID, + + JOIN_CHOOSESERVER, + JOIN_CONNECTING, + JOIN_CONNECTED, + JOIN_ERROR, + JOIN_EXCHANGE_CERT, + JOIN_VERIFY_CERT, + JOIN_EXCHANGE_KEYS, + JOIN_WAITING, + JOIN_SEND_CONNID +}; + +enum AppAlert +{ + ALERT_NONE, + ALERT_LOADINGPLAYERDATA, + ALERT_LISTENING, + ALERT_NATNEG, + ALERT_CONNECTING, + ALERT_REPLAY, + ALERT_REPLAY_CONFIRM, + ALERT_WAITING_FOR_LEADERS, + ALERT_NO_NETWORK_CONNECTION +}; + + +/////////////////////////////////////////////////////////////////////////////// +// Query and Reporting Callbacks +static void ServerKeyCallback(int keyid, qr2_buffer_t outbuf, void* userdata); +static void PlayerKeyCallback(int keyid, int index, qr2_buffer_t outbuf, void* userdata); +static void TeamKeyCallback(int keyid, int index, qr2_buffer_t outbuf, void* userdata); +static int CountCallback(qr2_key_type keytype, void* userdata); +static void KeyListCallback(qr2_key_type keytype, qr2_keybuffer_t keybuffer, void* userdata); +static void AddErrorCallback(qr2_error_t error, char* errmsg, void* userdata); +static void NatNegCallback(int cookie, void* userData); + +/////////////////////////////////////////////////////////////////////////////// +// Competition Callbacks +static void CreateSessionCallback(const SCInterfacePtr interface, GHTTPResult httpResult, SCResult result, void* userData); +static void SetReportIntentionCallback(const SCInterfacePtr interface, GHTTPResult httpResult, SCResult result, void* userData); +static void SubmitReportCallback(const SCInterfacePtr interface, GHTTPResult httpResult, SCResult result, void* userData); + +/////////////////////////////////////////////////////////////////////////////// +// Sake Callbacks +static void GetMyRecordsCallback(SAKE sake, SAKERequest request, SAKERequestResult result, SAKEGetMyRecordsInput* inputData, SAKEGetMyRecordsOutput* outputData, void* userData); + +/////////////////////////////////////////////////////////////////////////////// +// GT2 Callbacks +static void ConnectedCallback(GT2Connection connection, GT2Result result, GT2Byte* message, int len); +static void ReceivedCallback(GT2Connection connection, GT2Byte* message, int len, GT2Bool reliable); +static void ClosedCallback(GT2Connection connection, GT2CloseReason reason); +static void ConnectAttemptCallback(GT2Socket listener, GT2Connection connection, unsigned int ip, unsigned short port, int latency, GT2Byte* message, int len); +static void SocketErrorCallback(GT2Socket socket); +static GT2Bool UnrecognizedMessageCallback(GT2Socket socket, unsigned int ip, unsigned short port, GT2Byte* message, int len); + +/////////////////////////////////////////////////////////////////////////////// +// Leaderboard Callbacks +//static void cbGetBuddyList( GPConnection * connection, void * arg, void * param ); +static void GetMyLeaderPositionCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData); +static void SearchForLeadersTimeRecordsCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData); +static void SearchForTop5TimeRecordsCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData); +//static BOOL setTimeRecords(SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData); + +/////////////////////////////////////////////////////////////////////////////// +// NAT Negotiation Callbacks +static void NNDetectionCallback(gsi_bool success, NAT nat); +static void NNProgressCallback(NegotiateState state, void* userdata); +static void NNCompletedCallback(NegotiateResult result, SOCKET gamesocket, sockaddr_in* remoteaddr, void* userDdata); + +static const char PLAYER_STATS_TABLE[] = "PlayerStats_v" STRINGIZE(ATLAS_RULE_SET_VERSION); + +@implementation LeaderboardObject +@synthesize name; +@synthesize profileId; +@synthesize pictureFileId; +@synthesize rank; +@synthesize careerWins; +@synthesize careerLosses; +@synthesize bestRaceTime; +@synthesize totalMatches; +@synthesize currentWinStreak; +@synthesize playerLocation; +@synthesize thumbNail; + +- (CLLocationCoordinate2D)coordinate { + return playerLocation.coordinate; +} + +- (NSString *)title { + return name; +} + +@end + +// GLOBAL VARIABLES +//================= +char raceDateTimeString[256]; +PlayerData gPlayerData; +PlayerData gOpponentData; +//LeaderboardObject gOpponentData; // current opponent +int gLeaderboardType; // type of most recently downloaded leaderboard data +int gLeaderboardProfileIndex; // index of currently displayed profile +NSMutableArray* gLeaderboardData; // array of PlayerData for leaderboard +AppDelegate* appDelegate; +bool sdkInitialized; // set true at end of applicationDidFinishLaunching +bool userKickedOut; // set to true if same user logs into another device +bool lostConnection; // true when we lose network connectivity + +@interface AppDelegate(Private) + +//- (UIViewController*)pushNewControllerOfType: (Class)type nib: (NSString*)nibName; +- (UIViewController*)pushNewControllerOfType: (Class)type nib: (NSString*)nibName animated: (BOOL)animated; + +- (bool)setupHosting; +- (bool)setupJoining: (unsigned int)ip port: (unsigned short)port useNatNeg: (bool)useNatNeg; +- (void)setupMatch: (bool)hostMatch; + +- (void)onLoadRemoteDataTimer: (NSTimer*)timer; +- (void)onThinkTimer: (NSTimer*)timer; +- (void)onCountdownTimer: (NSTimer*)timer; +- (void)onHostingCountdownTimer: (NSTimer*)timer; + +- (void)saveTwoPlayerGameStatsToFile; + +- (void)reportTwoPlayerStats; +- (void)reportSinglePlayerStats; +- (void)countdown; +- (void)startRace; + +- (void)addErrorCallback: (qr2_error_t)error errorMessage: (const char*)errmsg; + +- (void)createSessionCallback: (const SCInterfacePtr)interface httpResult: (GHTTPResult)httpResult result: (SCResult)result; +- (void)setReportIntentionCallback: (const SCInterfacePtr)interface httpResult: (GHTTPResult)httpResult result: (SCResult)result; +- (void)submitReportCallback: (const SCInterfacePtr)interface httpResult: (GHTTPResult)httpResult result: (SCResult)result; + +- (void)getMyRecordsCallback: (SAKERequestResult) result inputData: (SAKEGetMyRecordsInput*)inputData outputData: (SAKEGetMyRecordsOutput*)outputData; + +- (void)connectedCallback: (GT2Connection)connection result: (GT2Result)result message: (GT2Byte*)message length: (int)length; +- (void)receivedCallback: (GT2Connection)connection message: (GT2Byte*)message length: (int)length reliable: (GT2Bool)reliable; +- (void)closedCallback: (GT2Connection)connection reason: (GT2CloseReason)reason; +- (void)connectAttemptCallback: (GT2Socket)listener connection: (GT2Connection)connection ip: (unsigned int)ip port: (unsigned short)port latency: (int)latency message: (GT2Byte*)message length: (int)length; + +- (void)natnegCallback: (int)cookie; +- (void)natnegCompletedCallback: (NegotiateResult)result socket: (SOCKET)gamesocket remoteaddr: (sockaddr_in*)remoteaddr; + +@end + + +@implementation AppDelegate + +@synthesize serverBrowser; +@synthesize sake; + +@synthesize connected; +@synthesize state; +@synthesize fromMenu; + +@synthesize start; +@synthesize localTime; +@synthesize remoteTime; + + +// Called when leaving TapRace +// =========================== +- (void)dealloc +{ + [self deleteLeaderboards]; + [self deleteBlockedImagesData]; + [window release]; + [navController release]; + [super dealloc]; +} + +#ifdef GSI_COMMON_DEBUG + +// Called to display SDK debug messages +// ==================================== +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); +} +#endif + + +// Called when race is finished or terminated +- (void)stopHostingServer +{ + qr2_shutdown(NULL); + +/* if (gt2Connection) + { + gt2CloseConnection(gt2Connection); + gt2Connection = NULL; + } + + GT2Socket tempsocket = gt2Socket; + gt2Socket = NULL; // prevent recursion + gt2CloseSocket(tempsocket); +*/ + connected = false; + hosting = false; + NSLog(@"stopHostingServer qr2_shutdown"); +} + + +// Called when click login or into login page +// ========================================== +- (void)initGame +{ + sessionCreated = false; + NSLog(@"initGame: null gt2 socket"); + gt2Socket = NULL; + state = LOGGED_OUT; + countdown = 0; + racing = false; + connIdSet = false; + gt2Connection = NULL; + gLeaderboardData = nil; + gLeaderboardType = LEADERBOARD_INVALID; +} + + +// Called when click login or into login page +// Initialize Sake - used to read stored data from the backend +// =========================================================== +- (void)initSake +{ + GSIACResult aResult = GSIACWaiting; + + if(sdkInitialized) return; + + // Do Availability Check - Make sure backend is available + GSIStartAvailableCheck(SCRACE_GAMENAME); + while( aResult == GSIACWaiting ) + { + aResult = GSIAvailableCheckThink(); + msleep(5); + } + + if ( aResult == GSIACUnavailable ) + { + MessageBox( @"Online service for TapRace is no longer available." ); + return; + //exit(EXIT_FAILURE); + } + + if ( aResult == GSIACTemporarilyUnavailable ) + { + MessageBox(@"Online service for TapRace is temporarily down for maintenance."); + return; + //exit(EXIT_FAILURE); + } + + // Initialize SDK core object - used for both the AuthService and the Competition SDK + gsCoreInitialize(); + + + // Initialize the Competition SDK - all users submit a snapshot + SCResult result = scInitialize(SCRACE_GAMEID, &interface); + + if (result != SCResult_NO_ERROR) + { + NSLog(@"SCResult result = %d", result ); + + switch (result) + { + case SCResult_OUT_OF_MEMORY: + MessageBox(@"Application failed to initialize.", @"Out of memory"); + break; + + case SCResult_NOT_INITIALIZED: + MessageBox(@"Application failed to initialize.", @"Competition"); + break; + + case SCResult_NO_AVAILABILITY_CHECK: + MessageBox(@"Application failed to initialize.", @"Availability"); + break; + + default: + MessageBox(@"Application failed to initialize.", @"Unknown Error"); + break; + } + return; + //exit(EXIT_FAILURE); + } + + gPlayerData.statsInterface = interface; + gPlayerData.thumbNail = nil; + gPlayerData.fullsizeImage = nil; + + sakeStartup(&sake); + sakeSetGame(sake, SCRACE_GAMENAME, SCRACE_GAMEID, SCRACE_SECRETKEY); + sdkInitialized = true; +} + + +// called after a delay to upload a new image from the image picker... +// gives the mainloop a chance to switch views and render the leaders menu again before the upload starts +// ======================================================================================================= +- (void)delayedNetworkConnectionErrorMessage +{ + MessageBox(@"Unable to establish connection. Please connect to a different WiFi network or switch to 3G.", + @"Network Error"); +} + + +// called after TapRace finishes loading +// ======================================================================================================= +- (void)applicationDidFinishLaunching: (UIApplication*)application +{ +#ifdef GSI_COMMON_DEBUG + // gsSetDebugFile(stdout); // output to console + // gsSetDebugCallback(DebugCallback); // or output to a function for logging + + // Set output debug levels + gsSetDebugLevel(GSIDebugCat_All, GSIDebugType_All, GSIDebugLevel_Verbose); // all SDKs +#endif + + appDelegate = self; + + [window addSubview: navController.view]; + [window makeKeyAndVisible]; +} + +// Perform a reachability query for the address 0.0.0.0. If that address is reachable without +// requiring a connection, a network interface is available. We'll have to do more work to +// determine which network interface is available. +// ======================================================================================================= +- (BOOL)isNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags +{ + SCNetworkReachabilityRef defaultRouteReachability; + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress); + + SCNetworkReachabilityFlags flags; + BOOL gotFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags); + if (!gotFlags) + return NO; + + BOOL isReachable = flags & kSCNetworkReachabilityFlagsReachable; + + // This flag indicates that the specified nodename or address can + // be reached using the current network configuration, but a + // connection must first be established. + // + // If the flag is false, we don't have a connection. But because CFNetwork + // automatically attempts to bring up a WWAN connection, if the WWAN reachability + // flag is present, a connection is not required. + BOOL noConnectionRequired = !(flags & kSCNetworkReachabilityFlagsConnectionRequired); + if ((flags & kSCNetworkReachabilityFlagsIsWWAN)) { + noConnectionRequired = YES; + } + + isReachable = (isReachable && noConnectionRequired) ? YES : NO; + + // Callers of this method might want to use the reachability flags, so if an 'out' parameter + // was passed in, assign the reachability flags to it. + if (outFlags) { + *outFlags = flags; + } + + return isReachable; +} + + +- (void)applicationWillTerminate: (UIApplication*)application +{ + if( hosting ) + { + qr2_shutdown(NULL); + } + + if (gPlayerData.thumbNail != nil) { + [gPlayerData.thumbNail release]; + gPlayerData.thumbNail = nil; + } + if (gPlayerData.fullsizeImage != nil) { + [gPlayerData.fullsizeImage release]; + gPlayerData.fullsizeImage = nil; + } + + // Note: gOpponentData does not use fullsizeImage + if (gOpponentData.thumbNail != nil) { + [gOpponentData.thumbNail release]; + gOpponentData.thumbNail = nil; + } + + [appDelegate deleteLeaderboards]; + + [self logout]; +} + +- (void)navigationController: (UINavigationController*)navigationController willShowViewController: (UIViewController*)viewController animated: (BOOL)animated +{ + if ([viewController isMemberOfClass: [LoginController class]]) { + if (state != LOGGED_OUT) + { + state = LOGGED_OUT; + NSLog(@"navigationController state=LOGGED_OUT"); + + LoginController* loginController = (LoginController*)viewController; + assert(loginController != nil); + [loginController reset]; + } + } + else if ([viewController isMemberOfClass: [MenuController class]]) { + if (state != SETTING_UP) + { + racing = false; + if (gt2Connection != NULL) + { + gt2CloseConnection(gt2Connection); + gt2Connection = NULL; + } + + [self stopThinkTimer]; + + state = SETTING_UP; + NSLog(@"navigationController state=SETTING_UP"); + + MenuController* menuController = (MenuController*)viewController; + assert(menuController != nil); + [menuController reset]; + } + } + else if ([viewController isMemberOfClass: [MatchmakingController class]]) { + if (state != JOIN_CHOOSESERVER) { + if (playerCount > 1) { + if (countdownTimer != nil) + { + [countdownTimer invalidate]; + countdownTimer = nil; + } + if (hostingCountdownTimer != nil) + { + [hostingCountdownTimer invalidate]; + hostingCountdownTimer = nil; + } + racing = false; + if (gt2Connection != NULL) + { + gt2CloseConnection(gt2Connection); + gt2Connection = NULL; + } + + state = JOIN_CHOOSESERVER; + NSLog(@"navigationController state=JOIN_CHOOSESERVER"); + + MatchmakingController* matchmakingController = (MatchmakingController*)viewController; + assert(matchmakingController != nil); + [matchmakingController reset]; + } + } + } + else if ([viewController isMemberOfClass: [GameController class]]) { + if (state != RACING) { + state = RACING; + NSLog(@"navigationController state=RACING"); + [gameController reset]; + } + } +} + +- (void)startThinkTimer +{ + if (thinkTimer != nil) + return; // already running + + thinkTimer = [NSTimer scheduledTimerWithTimeInterval: 0.050 target: self selector: @selector(onThinkTimer:) userInfo: nil repeats: YES]; + + [UIApplication sharedApplication].idleTimerDisabled = NO; +} + +- (void)stopThinkTimer +{ + [thinkTimer invalidate]; + thinkTimer = nil; + + [UIApplication sharedApplication].idleTimerDisabled = NO; +} + + +/** + * Once logged in, load the local file stats and initiate loading of server stats + */ +- (void)loggedIn +{ + NSString* dataPath; + + // Load locally saved player stats data. + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + + if ([paths count] == 0) { + // Something's very wrong; this should never happen. + MessageBox(@"Unable to access private data path in the file system.", @"Error"); + exit(EXIT_FAILURE); + } + + NSString* documentsPath = (NSString*)[paths objectAtIndex: 0]; + gPlayerData.playerDataPath = [[documentsPath stringByAppendingPathComponent: [NSString stringWithFormat: @"%d", gPlayerData.profileId]] retain]; + + // Check if diretory exists + BOOL isDir = NO; + NSFileManager* fileManager = [NSFileManager defaultManager]; + if (![fileManager fileExistsAtPath: gPlayerData.playerDataPath isDirectory: &isDir] || !isDir) + { + if (!isDir) + { + // Don't know how this got here, but we don't want it: + [fileManager removeItemAtPath: gPlayerData.playerDataPath error: NULL]; + } + } + + //*** Get the player's block image list from file + dataPath = [gPlayerData.playerDataPath stringByAppendingPathComponent: blockedImagesDataFile]; + gPlayerData.blockedImagesData = [[NSMutableArray alloc] initWithContentsOfFile: dataPath]; // will be nil if the file doesn't exist + if( gPlayerData.blockedImagesData == nil ) + gPlayerData.blockedImagesData = [[NSMutableArray alloc] init]; + + //*** Get the player stats from file + dataPath = [gPlayerData.playerDataPath stringByAppendingPathComponent: playerStatsDataFile]; + gPlayerData.playerStatsData = [[NSMutableDictionary alloc] initWithContentsOfFile: dataPath]; // will be nil if the file doesn't exist + + // Load single & multi-player data from server + static char* fields[] = + { + ATLAS_GET_STAT_NAME(CAREER_WINS), + ATLAS_GET_STAT_NAME(CAREER_LOSSES), + ATLAS_GET_STAT_NAME(BEST_RACE_TIME), + ATLAS_GET_STAT_NAME(WORST_RACE_TIME), + ATLAS_GET_STAT_NAME(TOTAL_MATCHES), + ATLAS_GET_STAT_NAME(AVERAGE_RACE_TIME), + ATLAS_GET_STAT_NAME(CURRENT_WIN_STREAK), + ATLAS_GET_STAT_NAME(CURRENT_LOSS_STREAK), + ATLAS_GET_STAT_NAME(TOTAL_RACE_TIME), + ATLAS_GET_STAT_NAME(CAREER_DISCONNECTS), + ATLAS_GET_STAT_NAME(DISCONNECT_RATE), + ATLAS_GET_STAT_NAME(CAREER_DRAWS), + ATLAS_GET_STAT_NAME(CURRENT_DRAW_STREAK), + ATLAS_GET_STAT_NAME(CAREER_LONGEST_WIN_STREAK), + ATLAS_GET_STAT_NAME(CAREER_LONGEST_LOSS_STREAK), + ATLAS_GET_STAT_NAME(CAREER_LONGEST_DRAW_STREAK), + ATLAS_GET_STAT_NAME(TOTAL_COMPLETE_MATCHES), + + ATLAS_GET_STAT_NAME(SP_BEST_RACE_TIME), + ATLAS_GET_STAT_NAME(SP_WORST_RACE_TIME), + ATLAS_GET_STAT_NAME(SP_TOTAL_PLAYS), + ATLAS_GET_STAT_NAME(SP_AVERAGE_RACE_TIME), + ATLAS_GET_STAT_NAME(SP_TOTAL_RACE_TIME), + ATLAS_GET_STAT_NAME(LAST_TIME_PLAYED) + }; + static SAKEGetMyRecordsInput input = { (char*)PLAYER_STATS_TABLE, fields, sizeof(fields) / sizeof(fields[0]) }; + + alertView = [[UIAlertView alloc] initWithTitle: nil message: @"Loading player data..." delegate: self cancelButtonTitle: nil otherButtonTitles: nil]; + [alertView show]; + + currentAlert = ALERT_LOADINGPLAYERDATA; + sakeSetProfile(sake, gPlayerData.profileId, gPlayerData.loginTicket); + sakeGetMyRecords(sake, &input, (SAKERequestCallback)GetMyRecordsCallback, self); + state = SETTING_UP; + NSLog(@"loggedIn state=SETTING_UP"); + + // display Menu page + [self pushNewControllerOfType: [MenuController class] nib: @"menu"]; + loadRemoteDataTimer = [NSTimer scheduledTimerWithTimeInterval: 0.050 target: self selector: @selector(onLoadRemoteDataTimer:) userInfo: nil repeats: YES]; +} + + +// Called when user receives message from server that +// - the login has been is disconnected on the server (due to same user login on other device) +// - the connection closed +- (void)kickedOut +{ + userKickedOut = true; +} + +- (bool)isUserKickedOut +{ + return userKickedOut; +} + + +// Called when logging user out +- (void)logout +{ + if (gt2Connection) + { + gt2CloseConnection(gt2Connection); + gt2Connection = NULL; + } + if (gt2Socket != NULL) + { + GT2Socket tempsocket = gt2Socket; + gt2Socket = NULL; // prevent recursion + NSLog(@"logout: null & close gt2 socket"); + gt2CloseSocket(tempsocket); + } + + NSLog(@"logout: qr2 shutdown"); + qr2_shutdown(NULL); + + sakeShutdown(sake); + scShutdown(interface); + gsCoreShutdown(); + + [gPlayerData.playerDataPath release]; + gPlayerData.playerDataPath = nil; + + LoginController* loginController = (LoginController*)[self findExistingControllerOfType: [LoginController class]]; + + [loginController reset]; + + [navController popToViewController: loginController animated: YES]; +} + +- (bool)joinGame: (unsigned int)ip port: (unsigned short)port useNatNeg: (bool)useNatNeg; +{ + if (![self setupJoining: ip port: port useNatNeg: useNatNeg]) + { + //MessageBox(@"Error setting up the match"); + MessageBox(@"Connection Error", @"Error setting up the match. Login when network connection is up"); +// was commented ***************** + if (gt2Connection) + { + gt2CloseConnection(gt2Connection); + gt2Connection = NULL; + } + if (gt2Socket != NULL) + { + GT2Socket tempsocket = gt2Socket; + gt2Socket = NULL; // prevent recursion + NSLog(@"joinGame: null & close gt2 socket"); + gt2CloseSocket(tempsocket); + } +// up to here ******************* + //[self logout]; // from old code + return false; + } + + return true; +} + +- (bool)hostGame +{ + if (![self setupHosting]) + { + // If timed out waiting for player + if (hostingCountdownTimer == nil || hostingCountdown != 0 ) + { + MessageBox(@"Error setting up the match."); + } + + return false; + } + return true; +} + +- (void)stopHostingCountdownTimer +{ + NSLog(@"stopHostingCountdownTimer"); + + if (hostingCountdownTimer != nil) + { + [hostingCountdownTimer invalidate]; + hostingCountdownTimer = nil; + } + +} + +- (void)tellHostToQuitGame +{ + // 2 player match - Let opponent know we finished. + ///////////////////////////// + char buffer[32]; + + int rcode = gtEncode(MSG_END_RACE_TYPE, MSG_END_RACE, buffer, sizeof(buffer), (uint32_t)(localTime * absolute_to_millis)); + assert(rcode != -1); + + NSLog(@"Protocol send MSG_END_RACE_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); +} + +- (void)findGame +{ + state = JOIN_CHOOSESERVER; + NSLog(@"findGame state=JOIN_CHOOSESERVER"); + playerCount = 2; + + [self pushNewControllerOfType: [MatchmakingController class] nib: @"matchmaking"]; + + [self startThinkTimer]; +} + +- (void)startSinglePlayerGame +{ + fromMenu = true; + state = RACING; + NSLog(@"startSinglePlayerGame state=RACING"); + reportSent = gsi_false; + playerCount = 1; + + gameController = (GameController*)[self pushNewControllerOfType: [GameController class] nib: @"singleplayergame"]; +} + +- (void)startMultiPlayerGame: (const char*)remoteAddress +{ + const int buflen = 2 + 4 + 2 + GP_UNIQUENICK_LEN; // 2 bytes for each message type, 4 bytes for int, GP_UNIQUENICK_LEN for max uniquenick length + char buf[buflen]; + gtEncodeNoType(MSG_CONNECT, buf, buflen, gPlayerData.profileId, gPlayerData.uniquenick); + + GT2ConnectionCallbacks callbacks = { + ConnectedCallback, + ReceivedCallback, + ClosedCallback, + NULL + }; + + NSLog(@"startMultiPlayerGame: connect gt2 socket"); + GT2Result result = gt2Connect(gt2Socket, >2Connection, remoteAddress, (GT2Byte*)buf, buflen, 0, &callbacks, GT2False); + NSLog(@"startMultiPlayerGame remoteAddress=%d", remoteAddress); + NSLog(@"startMultiPlayerGame gt2Connect:gt2Socket=%d result=%d\n", (int)gt2Socket, (int)result); + if (result != GT2Success) + { + MessageBox(@"Unable to connect to remote player.", @"Connection Error"); + return; + } + + gt2SetConnectionData(gt2Connection, self); + state = JOIN_CONNECTING; + NSLog(@"startMultiPlayerGame state=JOIN_CONNECTING"); + + // show that we are connecting + alertView = [[UIAlertView alloc] initWithTitle: @"Connecting to remote player" message: @"30 seconds" delegate: self cancelButtonTitle: nil otherButtonTitles: nil]; + [alertView show]; + currentAlert = ALERT_CONNECTING; + + [appDelegate startJoiningCountdownTimer]; +} + +- (bool)isTwoPlayerGame +{ + return (playerCount == 2); +} + +- (void)startCountdown +{ + if (playerCount == 1) + { + reportSent = false; + scCreateMatchlessSession(interface, &gPlayerData.certificate, &gPlayerData.privateData, CreateSessionCallback, SCRACE_TIMEOUT_MS, self); + + // Single-player game, no countdown. + [self startRace]; + return; + } + + // release joining game countdown + [self stopJoiningCountdownTimer]; + + if (hosting) + { + // The countdown here could be thought of as a loading screen. + // During this loading phase - the Host will create the game session and notify + // the other players of the session ID. All players will set their report intentions. + scCreateSession(interface, &gPlayerData.certificate, &gPlayerData.privateData, CreateSessionCallback, SCRACE_TIMEOUT_MS, self); + state = HOST_SEND_SESSID; + NSLog(@"startCountdown state=HOST_SEND_SESSID"); + + // Start the countdown. + countdown = COUNTDOWN_START; + countdownTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @selector(onCountdownTimer:) userInfo: nil repeats: YES]; + [self countdown]; + } +} + +- (void)startHostingCountdownTimer +{ + // release hosting countdown + [self stopHostingCountdownTimer]; //if any + + // Start the countdown. + NSLog(@"startHostingCountdownTimer starts hostingCountdownTimer"); + hostingCountdown = 30; + hostingCountdownTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @selector(onHostingCountdownTimer:) userInfo: self repeats: YES]; + +} + +- (void)stopJoiningCountdownTimer +{ + NSLog(@"stopJoiningCountdownTimer"); + if (joiningCountdownTimer != nil) + { + [joiningCountdownTimer invalidate]; + joiningCountdownTimer = nil; + } + +} + +- (void)startJoiningCountdownTimer +{ + [self stopJoiningCountdownTimer]; // if running + + NSLog(@"startJoiningCountdownTimer"); + + // Start the countdown. + joiningCountdown = 30; + joiningCountdownTimer = [NSTimer scheduledTimerWithTimeInterval: 1.0 target: self selector: @selector(onJoiningCountdownTimer:) userInfo: nil repeats: YES]; + +} + + +- (void)restartGame +{ + if (playerCount > 1) + { + char buffer[32]; + int rcode = gtEncode(MSG_REPLAY_TYPE, MSG_REPLAY, buffer, sizeof(buffer)); + + assert(rcode != -1); + NSLog(@"Protocol send MSG_REPLAY_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + + alertView = [[UIAlertView alloc] initWithTitle: @"Waiting for reply" message: @"30 seconds" delegate: self cancelButtonTitle: nil otherButtonTitles: nil]; + [alertView show]; + currentAlert = ALERT_REPLAY; + + [self startHostingCountdownTimer]; + } + else + { + assert(gameController != nil); + + [navController popToViewController: gameController animated: YES]; + } +} + +- (void)finishedGame +{ + fromMenu = false; + + BOOL newBestTime = NO; + localTime = mach_absolute_time() - start; + + + // set last game date/time + NSString *raceTimeString = [[NSDate date] description]; + strcpy( raceDateTimeString, [raceTimeString cStringUsingEncoding: NSASCIIStringEncoding] ); + gPlayerData.lastGameTime = raceDateTimeString; + + if (playerCount == 1) + { + int oldBestTime; + GameResultsController* resultsController = (GameResultsController*)[self pushNewControllerOfType: [GameResultsController class] nib: @"singleplayerresults"]; + state = FINISHED_RACE; + NSLog(@"finishedGame state=FINISHED_RACE"); + + // Update single player data. + int localTimeMillis = localTime * absolute_to_millis; + float avgTapsPerSec = 60.0 / (localTimeMillis / 1000.0); + + if (gPlayerData.playerStatsData != nil) + { + // data from file + int bestTimeMillis = [(NSNumber*)[gPlayerData.playerStatsData objectForKey: singlePlayerBestTimeKey] intValue]; + int worstTimeMillis = [(NSNumber*)[gPlayerData.playerStatsData objectForKey: singlePlayerWorstTimeKey] intValue]; + int averageTimeMillis = [(NSNumber*)[gPlayerData.playerStatsData objectForKey: singlePlayerAverageTimeKey] intValue]; + int gamesPlayed = [(NSNumber*)[gPlayerData.playerStatsData objectForKey: singlePlayerGamesPlayedKey] intValue]; + + averageTimeMillis = ((averageTimeMillis * gamesPlayed) + localTimeMillis) / (gamesPlayed + 1); + gamesPlayed++; + + + if (bestTimeMillis > localTimeMillis) + { + newBestTime = YES; + oldBestTime = bestTimeMillis; + + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: localTimeMillis] forKey: singlePlayerBestTimeKey]; + } + else + { + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: bestTimeMillis] forKey: singlePlayerBestTimeKey]; + } + + if (worstTimeMillis < localTimeMillis) + worstTimeMillis = localTimeMillis; + + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: worstTimeMillis] forKey: singlePlayerWorstTimeKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: averageTimeMillis] forKey: singlePlayerAverageTimeKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gamesPlayed] forKey: singlePlayerGamesPlayedKey]; + } + else + { + gPlayerData.playerStatsData = [[NSMutableDictionary alloc] initWithObjectsAndKeys: + [NSNumber numberWithInt: localTimeMillis], singlePlayerBestTimeKey, + [NSNumber numberWithInt: localTimeMillis], singlePlayerWorstTimeKey, + [NSNumber numberWithInt: localTimeMillis], singlePlayerAverageTimeKey, + [NSNumber numberWithInt: 1], singlePlayerGamesPlayedKey, + nil]; + } + + [gPlayerData.playerStatsData setObject: raceTimeString forKey: lastTimePlayedKey]; + + [gPlayerData.playerStatsData writeToFile: [gPlayerData.playerDataPath stringByAppendingPathComponent: playerStatsDataFile] atomically: YES]; + + // Report the stats. + //////////////////// + if (!reportSent) + { + disconnected = gsi_false; + [self reportSinglePlayerStats]; + } + + [resultsController setGameTime: localTimeMillis]; + [resultsController setGamesPlayed: [(NSNumber*)[gPlayerData.playerStatsData objectForKey: singlePlayerGamesPlayedKey] intValue]]; + [resultsController setBestTime: [(NSNumber*)[gPlayerData.playerStatsData objectForKey: singlePlayerBestTimeKey] intValue]]; + [resultsController setAverageTime: [(NSNumber*)[gPlayerData.playerStatsData objectForKey: singlePlayerAverageTimeKey] intValue]]; + [resultsController setAverageTaps: avgTapsPerSec ]; + if (newBestTime) + { + [resultsController setNewBestTime: oldBestTime]; + } + } + else + { + // 2 player match - Let opponent know we finished. + ///////////////////////////// + char buffer[32]; + + int rcode = gtEncode(MSG_END_RACE_TYPE, MSG_END_RACE, buffer, sizeof(buffer), (uint32_t)(localTime * absolute_to_millis)); + assert(rcode != -1); + + NSLog(@"Protocol send MSG_END_RACE_TYPE\n"); + GT2Result result = gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + if( result == GT2NetworkError ) + { + MessageBox(@"Lost connection to remote player. Game canceled", @"Connection Error"); + [appDelegate returnToMatchMaking]; + } + } + +} + + +// save single player and two player game stats for logged in player +- (void)savePlayerStatsToFile +{ + if (gPlayerData.playerStatsData == nil) + { + // Copy stats from server to new file + gPlayerData.playerStatsData = [[NSMutableDictionary alloc] initWithObjectsAndKeys: + // single player game stats + [NSNumber numberWithInt: gPlayerData.spBestRaceTime], singlePlayerBestTimeKey, + [NSNumber numberWithInt: gPlayerData.spWorstRaceTime], singlePlayerWorstTimeKey, + [NSNumber numberWithInt: gPlayerData.spTotalPlays], singlePlayerTotalPlaysKey, + [NSNumber numberWithInt: gPlayerData.spTotalRaceTime], singlePlayerTotalRaceTimeKey, + [NSNumber numberWithInt:(int) (gPlayerData.spAverageRaceTime *1000)], singlePlayerAverageTimeKey, + + // two player game stats + [NSNumber numberWithInt: gPlayerData.bestRaceTime], matchBestTimeKey, + [NSNumber numberWithInt: gPlayerData.worstRaceTime], matchWorstTimeKey, + [NSNumber numberWithInt: (int)(gPlayerData.averageRaceTime * 1000)], matchAverageTimeKey, + [NSNumber numberWithInt: gPlayerData.careerWins], matchWonKey, + [NSNumber numberWithInt: gPlayerData.careerLosses], matchLossKey, + [NSNumber numberWithInt: gPlayerData.matchWinStreak], matchWinStreakKey, + [NSNumber numberWithInt: gPlayerData.totalMatches], matchGamesPlayedKey, + [NSNumber numberWithInt: gPlayerData.matchLossStreak], matchLossStreakKey, + [NSNumber numberWithInt: gPlayerData.totalRaceTime], matchTotalRaceTimeKey, + [NSNumber numberWithInt: gPlayerData.careerDisconnects], matchCareerDisconnectsKey, + [NSNumber numberWithInt: gPlayerData.careerDraws], matchDrawsKey, + [NSNumber numberWithInt: gPlayerData.matchDrawStreak], matchDrawStreakKey, + [NSNumber numberWithInt: gPlayerData.careerLongestWinStreak], matchCareerLongestWinStreakKey, + [NSNumber numberWithInt: gPlayerData.careerLongestLossStreak], matchCareerLongestLossStreakKey, + [NSNumber numberWithInt: gPlayerData.careerLongestDrawStreak], matchCareerLongestDrawStreakKey, + [NSNumber numberWithInt: gPlayerData.totalCompleteMatches], matchTotalCompleteMatchesKey, + [NSNumber numberWithFloat: gPlayerData.disconnectRate], matchDisconnectRateKey, + [NSString stringWithCString: gPlayerData.lastGameTime encoding: NSASCIIStringEncoding], lastTimePlayedKey, + nil]; + } + else + { + // single player game stats + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.spBestRaceTime] forKey: singlePlayerBestTimeKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.spWorstRaceTime] forKey: singlePlayerWorstTimeKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.spTotalPlays] forKey: singlePlayerTotalPlaysKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.spTotalRaceTime] forKey: singlePlayerTotalRaceTimeKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithFloat: gPlayerData.spAverageRaceTime] forKey: singlePlayerAverageTimeKey]; + + // two player game stats + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.bestRaceTime] forKey: matchBestTimeKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.worstRaceTime] forKey: matchWorstTimeKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithFloat: gPlayerData.averageRaceTime] forKey: matchAverageTimeKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerWins] forKey: matchWonKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerLosses] forKey: matchLossKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchWinStreak] forKey: matchWinStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.totalMatches] forKey: matchGamesPlayedKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchLossStreak] forKey: matchLossStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.totalRaceTime] forKey: matchTotalRaceTimeKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerDisconnects] forKey: matchCareerDisconnectsKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerDraws] forKey: matchDrawsKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchDrawStreak] forKey: matchDrawStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerLongestWinStreak] forKey: matchCareerLongestWinStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerLongestLossStreak] forKey: matchCareerLongestLossStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerLongestDrawStreak] forKey: matchCareerLongestDrawStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.totalCompleteMatches] forKey: matchTotalCompleteMatchesKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithFloat: gPlayerData.disconnectRate] forKey: matchDisconnectRateKey]; + [gPlayerData.playerStatsData setObject: [NSString stringWithCString: gPlayerData.lastGameTime encoding: NSASCIIStringEncoding] forKey: lastTimePlayedKey]; + + } + + [gPlayerData.playerStatsData writeToFile: [gPlayerData.playerDataPath stringByAppendingPathComponent: playerStatsDataFile] atomically: YES]; +} + + +- (void)returnToMenu +{ + MenuController* controller = (MenuController*)[self findExistingControllerOfType: [MenuController class]]; + assert(controller != nil); + [navController popToViewController: controller animated: YES]; +} + + +- (void)returnToMatchMaking +{ + MatchmakingController* controller = (MatchmakingController*)[self findExistingControllerOfType: [MatchmakingController class]]; + assert(controller != nil); + [navController popToViewController: controller animated: YES]; +} + + + + - (void)showAboutInfo: (id) sender + { + FlipsideViewController *controller = [[FlipsideViewController alloc] initWithNibName:@"FlipsideView" bundle:nil]; + controller.delegate = sender; + + controller.modalTransitionStyle = UIModalTransitionStyleFlipHorizontal; + [sender presentModalViewController:controller animated:YES]; + + [controller release]; + } + + - (void)flipsideViewControllerDidFinish:(id) sender + { + [sender dismissModalViewControllerAnimated:YES]; + } + + +- (void)showLeaderboards +{ + [self deleteLeaderboards]; // delete leaderboards unconditionally, as data may have changed since last time + [self pushNewControllerOfType: [LeaderboardsController class] nib: @"leaderboards"]; +} + + + +- (void)downloadTop5Leaders +{ + static SAKESearchForRecordsInput input; + static SAKERequest request; + static char *fieldNames[] = { + (char *) "ownerid" , + (char *) "row", + ATLAS_GET_STAT_NAME( NICK ), + ATLAS_GET_STAT_NAME( BEST_RACE_TIME ), + ATLAS_GET_STAT_NAME( CURRENT_WIN_STREAK ), + ATLAS_GET_STAT_NAME( CAREER_WINS ), + ATLAS_GET_STAT_NAME( CAREER_LOSSES ), + ATLAS_GET_STAT_NAME( TOTAL_COMPLETE_MATCHES ), + ATLAS_GET_STAT_NAME( LATITUDE ), + ATLAS_GET_STAT_NAME( LONGITUDE) + }; + SAKEStartRequestResult startRequestResult; + + // empty leader board + [self deleteLeaderboards]; + gLeaderboardType = LEADERBOARD_BY_TIME; + + // get Top 5 leaders + input.mTableId = (char *) PLAYER_STATS_TABLE; + input.mFieldNames = fieldNames; + input.mNumFields = (sizeof(fieldNames) / sizeof(fieldNames[0])); + input.mOffset = 0; + input.mMaxRecords = 5; + input.mCacheFlag = gsi_true; // caches the TOP 5 query + input.mFilter = (char *) "BEST_RACE_TIME > 0 and BEST_RACE_TIME != 99999"; + input.mSort = (char *) "BEST_RACE_TIME asc"; + + request = sakeSearchForRecords(sake, &input, SearchForTop5TimeRecordsCallback, self); + if(!request) + { + startRequestResult = sakeGetStartRequestResult(sake); + NSLog(@"Failed to start request: %d\n", startRequestResult); + } + + currentAlert = ALERT_WAITING_FOR_LEADERS; +} + +- (void)getMyLeaderPosition +{ + static char aFilter[128]; + static SAKEGetRecordCountInput input; + static SAKERequest request; + + input.mTableId = (char *) PLAYER_STATS_TABLE; + input.mCacheFlag = gsi_true; // caches the TOP 10 query + sprintf( aFilter, "BEST_RACE_TIME < %d", gPlayerData.bestRaceTime ); + input.mFilter = (char *) aFilter; + + request = sakeGetRecordCount(sake, &input, GetMyLeaderPositionCallback, self); + if(!request) + { + gPlayerData.rank = -1; + + SAKEStartRequestResult startRequestResult; + startRequestResult = sakeGetStartRequestResult(sake); + NSLog( @"Failed to start request: %d\n", startRequestResult ); + } +} + + +- (void)downloadLeaders +{ + static SAKESearchForRecordsInput input; + static SAKERequest request; + static char *fieldNames[] = { + (char *) "ownerid" , + (char *) "row", + ATLAS_GET_STAT_NAME( NICK ), + ATLAS_GET_STAT_NAME( BEST_RACE_TIME ), + ATLAS_GET_STAT_NAME( CURRENT_WIN_STREAK ), + ATLAS_GET_STAT_NAME( CAREER_WINS ), + ATLAS_GET_STAT_NAME( CAREER_LOSSES ), + ATLAS_GET_STAT_NAME( TOTAL_COMPLETE_MATCHES ), + ATLAS_GET_STAT_NAME( LATITUDE ), + ATLAS_GET_STAT_NAME( LONGITUDE) + }; + SAKEStartRequestResult startRequestResult; + input.mTableId = (char *) PLAYER_STATS_TABLE; + input.mFieldNames = fieldNames; + input.mNumFields = (sizeof(fieldNames) / sizeof(fieldNames[0])); + input.mOffset = 0; + input.mMaxRecords = MAX_LEADERS; + input.mCacheFlag = gsi_true; // caches the TOP 10 query + + switch (gLeaderboardType) { + case LEADERBOARD_BY_TIME: + input.mFilter = (char *) "BEST_RACE_TIME > 0 and BEST_RACE_TIME != 99999"; + input.mSort = (char *) "BEST_RACE_TIME asc"; + + request = sakeSearchForRecords(sake, &input, SearchForLeadersTimeRecordsCallback, self); + if(!request) + { + startRequestResult = sakeGetStartRequestResult(sake); + NSLog(@"Failed to start request: %d\n", startRequestResult); + } + break; + } + alertView = [[UIAlertView alloc] initWithTitle: nil message: @"Waiting for leaderboards..." delegate: self cancelButtonTitle: nil otherButtonTitles: nil]; + [alertView show]; + currentAlert = ALERT_WAITING_FOR_LEADERS; +} + +- (BOOL)setTimeRecords: (SAKE)sake request: (SAKERequest)request result: (SAKERequestResult)result inputData: (void*)inputData outputData: (void*)outputData +{ + float lat,lng; + + SAKESearchForRecordsOutput * records = (SAKESearchForRecordsOutput*)outputData; + + if (result == SAKERequestResult_SUCCESS) + { + if (gLeaderboardData != nil) + { + int lbtype = gLeaderboardType; + [self deleteLeaderboards]; + gLeaderboardType = lbtype; + } + + if (records->mNumRecords > 0) + { + gLeaderboardData = [[NSMutableArray arrayWithCapacity: records->mNumRecords] retain]; + for (int i=0; imNumRecords; i++) + { + LeaderboardObject * pData = [[LeaderboardObject alloc] init]; + + pData.pictureFileId = 0; // init picture file id + + SAKEField * field = sakeGetFieldByName( "ownerid", records->mRecords[i], 10 ); + if (field != NULL) + pData.profileId = field->mValue.mInt; + else + pData.profileId = 0; + + field = sakeGetFieldByName( "row", records->mRecords[i], 10); + if (field != NULL) + pData.rank = field->mValue.mInt; + else + pData.rank = -1; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( NICK ), records->mRecords[i], 10 ); + if (field != NULL) + { + pData.name = [NSString stringWithFormat: @"%s", (char*)( field->mValue.mAsciiString ) ]; + } + else + pData.name = nil; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( BEST_RACE_TIME ), records->mRecords[i], 10 ); + if (field != NULL) + pData.bestRaceTime = (gsi_u32)( field->mValue.mInt ); + else + pData.bestRaceTime = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( TOTAL_COMPLETE_MATCHES ), records->mRecords[i], 10 ); + if (field != NULL) + pData.totalMatches = (gsi_u32)( field->mValue.mInt ); + else + pData.totalMatches = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( CAREER_WINS ), records->mRecords[i], 10 ); + if (field != NULL) + pData.careerWins = (gsi_u32)( field->mValue.mInt ); + else + pData.careerWins = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( CAREER_LOSSES ), records->mRecords[i], 10 ); + if (field != NULL) + pData.careerLosses = (gsi_u32)( field->mValue.mInt ); + else + pData.careerLosses = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( CURRENT_WIN_STREAK ), records->mRecords[i], 10 ); + if (field != NULL) + pData.currentWinStreak = (gsi_u32)( field->mValue.mInt ); + else + pData.currentWinStreak = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( LATITUDE ), records->mRecords[i], 10 ); + if (field != NULL) + lat = field->mValue.mFloat; + else + lat = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( LONGITUDE ), records->mRecords[i], 10 ); + if (field != NULL) + lng = field->mValue.mFloat; + else + lng = 0; + + pData.playerLocation = [[CLLocation alloc] initWithLatitude: lat longitude: lng]; + [gLeaderboardData insertObject: pData atIndex: i]; + NSLog(@"leader[%d] = %d\n",i, pData.profileId); + } + } + + return YES; + } + else + { + // error + return NO; + } +} + +- (void)searchForTop5TimeRecordsCallback: (SAKE)sake request: (SAKERequest)request result: (SAKERequestResult)result inputData: (void*)inputData outputData: (void*)outputData +{ + float lat,lng; + + SAKESearchForRecordsOutput * records = (SAKESearchForRecordsOutput*)outputData; + + if (result == SAKERequestResult_SUCCESS) { + if (gLeaderboardData != nil) + { + int lbtype = gLeaderboardType; + [self deleteLeaderboards]; + gLeaderboardType = lbtype; + } + + if (records->mNumRecords <= 0) + { + MenuController* menuController = (MenuController*)[self findExistingControllerOfType: [MenuController class]]; + [ menuController noTop5Leaders]; + } + else + { + gLeaderboardData = [[NSMutableArray arrayWithCapacity: records->mNumRecords] retain]; + for (int i=0; imNumRecords; i++) + { + LeaderboardObject * pData = [[LeaderboardObject alloc] init]; + + SAKEField * field = sakeGetFieldByName( "ownerid", records->mRecords[i], 10 ); + if (field != NULL) + pData.profileId = field->mValue.mInt; + else + pData.profileId = 0; + + field = sakeGetFieldByName( "row", records->mRecords[i], 10); + if (field != NULL) + pData.rank = field->mValue.mInt; + else + pData.rank = -1; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( NICK ), records->mRecords[i], 10 ); + if (field != NULL) + { + pData.name = [NSString stringWithFormat: @"%s", (char*)( field->mValue.mAsciiString ) ]; + } + else + pData.name = nil; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( BEST_RACE_TIME ), records->mRecords[i], 10 ); + if (field != NULL) + pData.bestRaceTime = (gsi_u32)( field->mValue.mInt ); + else + pData.bestRaceTime = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( TOTAL_COMPLETE_MATCHES ), records->mRecords[i], 10 ); + if (field != NULL) + pData.totalMatches = (gsi_u32)( field->mValue.mInt ); + else + pData.totalMatches = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( CAREER_WINS ), records->mRecords[i], 10 ); + if (field != NULL) + pData.careerWins = (gsi_u32)( field->mValue.mInt ); + else + pData.careerWins = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( CAREER_LOSSES ), records->mRecords[i], 10 ); + if (field != NULL) + pData.careerLosses = (gsi_u32)( field->mValue.mInt ); + else + pData.careerLosses = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( CURRENT_WIN_STREAK ), records->mRecords[i], 10 ); + if (field != NULL) + pData.currentWinStreak = (gsi_u32)( field->mValue.mInt ); + else + pData.currentWinStreak = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( LATITUDE ), records->mRecords[i], 10 ); + if (field != NULL) + lat = field->mValue.mFloat; + else + lat = 0; + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( LONGITUDE ), records->mRecords[i], 10 ); + if (field != NULL) + lng = field->mValue.mFloat; + else + lng = 0; + + pData.playerLocation = [[CLLocation alloc] initWithLatitude: lat longitude: lng]; + [gLeaderboardData insertObject: pData atIndex: i]; + } + } + } + else + { + // error + } + // get data + //[self setTimeRecords: sake request: request result: result inputData: inputData outputData: outputData ]; +} + + +- (void) getMyLeaderPositionCallback: (SAKE) aSake request: (SAKERequest)request result: (SAKERequestResult)result inputData: (void*)inputData outputData: (void*)outputData +{ + SAKEGetRecordCountOutput * records = (SAKEGetRecordCountOutput*)outputData; + + // need to pass in pointer to rank variable (int *)rankVar + if( result != SAKERequestResult_SUCCESS ) + gPlayerData.rank = 0; + else + gPlayerData.rank = records->mCount + 1; // 1 + number of records before my best time +} + +- (void)searchForLeadersTimeRecordsCallback: (SAKE) aSake request: (SAKERequest)request result: (SAKERequestResult)result inputData: (void*)inputData outputData: (void*)outputData +{ + // Got data - hide popup + if (alertView != nil) + { + //[alertView dismissWithClickedButtonIndex: alertView.cancelButtonIndex animated: YES]; + [alertView dismissWithClickedButtonIndex: alertView.firstOtherButtonIndex animated: YES]; + [alertView release]; + alertView = nil; + } + currentAlert = ALERT_NONE; + + if( [self setTimeRecords: aSake request: request result: result inputData: inputData outputData: outputData ] ) + { + // display leaderboard page + [self pushNewControllerOfType: [LeadersController class] nib: @"leadersbytime"]; + } + else + { + // error + } +} + + +- (void)showTop5LeadersByTime +{ + [self downloadTop5Leaders]; +} + +- (void)showLeadersByTime +{ + [self deleteLeaderboards]; + gLeaderboardType = LEADERBOARD_BY_TIME; + [self downloadLeaders]; +} + + +- (void)showLeadersByBuddies +{ + GPBuddyStatus status; + int numBuddies; + + [self deleteLeaderboards]; + gLeaderboardType = LEADERBOARD_BY_BUDDIES; + + GPResult res; + LoginController* loginController = (LoginController*)[self findExistingControllerOfType: [LoginController class]]; + + res = gpGetNumBuddies( [loginController getGPConnectionPtr], &numBuddies ); + if ( (res == GP_NO_ERROR) && (numBuddies == 0)) { + // just go to other pager where a no-buddies message will pop up + [self pushNewControllerOfType: [LeadersController class] nib: @"leadersbytime"]; + + } else { + if (res == GP_NO_ERROR) { + // create an array of buddies...since we only want the 1st MAX_BUDDIES, use a static array + static GPProfile buddyProfileIds[MAX_BUDDIES]; + for (int i=0; (i < MAX_BUDDIES) && (i < numBuddies); i++) + { + if (GP_NO_ERROR == gpGetBuddyStatus( [loginController getGPConnectionPtr], i, &status )) + { + buddyProfileIds[i] = status.profile; + } + else + break; + } + + static SAKESearchForRecordsInput input; + SAKERequest request; + static char *fieldNames[] = { + (char *) "ownerid" , + (char *) "row" , + ATLAS_GET_STAT_NAME( NICK ) , + ATLAS_GET_STAT_NAME( BEST_RACE_TIME ) , + ATLAS_GET_STAT_NAME( CURRENT_WIN_STREAK ) , + ATLAS_GET_STAT_NAME( CAREER_WINS ) , + ATLAS_GET_STAT_NAME( CAREER_LOSSES ) , + ATLAS_GET_STAT_NAME( TOTAL_COMPLETE_MATCHES ) , + ATLAS_GET_STAT_NAME( LATITUDE ) , + ATLAS_GET_STAT_NAME( LONGITUDE) + }; + + SAKEStartRequestResult startRequestResult; + + input.mTableId = (char *) PLAYER_STATS_TABLE; + input.mFieldNames = fieldNames; + input.mNumFields = (sizeof(fieldNames) / sizeof(fieldNames[0])); + input.mFilter = (char *) "BEST_RACE_TIME > 0 and BEST_RACE_TIME != 99999"; + input.mSort = (char *) "BEST_RACE_TIME asc"; + //input.mSort = (char *) "recordid asc"; + input.mOffset = 0; + input.mMaxRecords = MAX_BUDDIES; + input.mOwnerIds = buddyProfileIds; + input.mNumOwnerIds = numBuddies; + //input.mCacheFlag = gsi_true; + + request = sakeSearchForRecords(sake, &input, SearchForLeadersTimeRecordsCallback, self); + if(!request) + { + startRequestResult = sakeGetStartRequestResult(sake); + NSLog(@"Failed to start buddylist request: %d\n", startRequestResult); + } + } + } +} + +- (void)showLeadersByLocation +{ + [self deleteLeaderboards]; + gLeaderboardType = LEADERBOARD_BY_LOCATION; + LeadersByLocationController * newview = (LeadersByLocationController *)[self pushNewControllerOfType: [LeadersByLocationController class] nib: @"leadersbylocation"]; + [newview searchLeaderboardsByLocation]; +} + +// show the profile of a user in the leaderboard array, indexed by index +- (void)showUser: (int) index +{ + gLeaderboardProfileIndex = index; + [self pushNewControllerOfType: [UserStatsController class] nib: @"UserStats"]; +} + +- (UIViewController*)findExistingControllerOfType: (Class)type +{ + NSArray* viewControllers = navController.viewControllers; + NSUInteger index = [viewControllers count]; + + while (index > 0) + { + UIViewController* viewController = [viewControllers objectAtIndex: --index]; + + if ([viewController isKindOfClass: type]) + return viewController; + } + + return nil; +} + +- (void)deleteLeaderboards +{ + if (gLeaderboardData != nil) { + for (int i=[gLeaderboardData count]-1; i>=0; i--) + { + LeaderboardObject * pData = (LeaderboardObject*)[gLeaderboardData objectAtIndex: i]; + if (pData.playerLocation != nil) + { + pData.playerLocation = nil; // this is a property, auto-releases + } + if (pData.name != nil) + { + pData.name = nil; // this is a property, auto-releases + } + if (pData.thumbNail != nil) + { + pData.thumbNail = nil; // this is a property, auto-releases + } + [gLeaderboardData removeObjectAtIndex: i]; + } + + [gLeaderboardData release]; + gLeaderboardData = nil; + } + gLeaderboardType = LEADERBOARD_INVALID; +} + +- (void)deleteBlockedImagesData +{ + if( gPlayerData.blockedImagesData != nil ) + { + for( int i = 0; i < (int)[gPlayerData.blockedImagesData count]; i++ ) + { + [gPlayerData.blockedImagesData removeObjectAtIndex: i]; + } + + [gPlayerData.blockedImagesData release]; + gPlayerData.blockedImagesData = nil; + + MessageBox(@"Cleared all blocked images."); + } +} + + +// Remove leaders that have a default time of 99.999 +- (void)removeFalseLeaders +{ + if (gLeaderboardData != nil) + { + for (int i=[gLeaderboardData count]-1; i>=0; i--) + { + LeaderboardObject * pData = (LeaderboardObject*)[gLeaderboardData objectAtIndex: i]; + + // Remove entry if player has default best time + if (pData.bestRaceTime == INITIAL_BEST_TIME ) + { + if (pData.playerLocation != nil) + pData.playerLocation = nil; // this is a property, auto-releases + + if (pData.name != nil) + pData.name = nil; // this is a property, auto-releases + + if (pData.thumbNail != nil) + pData.thumbNail = nil; // this is a property, auto-releases + + [gLeaderboardData removeObjectAtIndex: i]; + } + + if( [gLeaderboardData count] == 0 ) + { + [gLeaderboardData release]; + gLeaderboardData = nil; + gLeaderboardType = LEADERBOARD_INVALID; + } + } + } +} + +- (UIViewController*)pushNewControllerOfType: (Class)type nib: (NSString*)nibName +{ + return [self pushNewControllerOfType: type nib: nibName animated: YES]; +} + +- (bool)imageIsBlocked: (int) pictureId +{ + int numBlockedImages = [gPlayerData.blockedImagesData count]; + if( pictureId == 0 || numBlockedImages == 0 ) + return false; + + NSNumber* fileIdNum = [[NSNumber alloc] initWithUnsignedInt: pictureId ]; + + for (int i = 0; i < numBlockedImages; i++ ) + { + if( [[gPlayerData.blockedImagesData objectAtIndex: i ] isEqualToNumber:fileIdNum ] ) + return true; + } + + return false; +} + + +@end + + +@implementation AppDelegate(Private) + +// Called when an alertView button is tapped. +/*- (void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex +{ + // 0 index is "Cancel" button + if(alertView.cancelButtonIndex >= 0 && buttonIndex == alertView.cancelButtonIndex) + { + [self stopHostingCountdownTimer]; + } +} +*/ + +// Called by system mainloop when animation of alert view is finished +- (void)alertView: (UIAlertView*)alert didDismissWithButtonIndex: (NSInteger)buttonIndex +{ + switch (currentAlert) + { + case ALERT_LOADINGPLAYERDATA: + if( buttonExists(alertView.cancelButtonIndex) && buttonIndex == alertView.cancelButtonIndex) + { + [loadRemoteDataTimer invalidate]; + [self logout]; + } + break; + + case ALERT_LISTENING: + NSLog(@"ALERT_LISTENING"); + if( buttonExists(alertView.cancelButtonIndex) && buttonIndex == alertView.cancelButtonIndex) + { + NSLog(@"ALERT_LISTENING: qr2 shutdown"); + qr2_shutdown(NULL); // I would like this to de-list the host from the master server, but it doesn't seem to do that. + + if (gt2Connection) + { + gt2CloseConnection(gt2Connection); + gt2Connection = NULL; + } + + GT2Socket tempsocket = gt2Socket; + gt2Socket = NULL; // prevent recursion + NSLog(@"ALERT_LISTENING: null & close gt2 socket"); + gt2CloseSocket(tempsocket); + + MatchmakingController* matchmakingcontroller = (MatchmakingController*)[self findExistingControllerOfType: [MatchmakingController class]]; + [matchmakingcontroller hostingCanceled]; + } + break; + + case ALERT_NATNEG: + NSLog(@"ALERT_NATNEG"); + if (buttonExists(alertView.cancelButtonIndex) && buttonIndex == alertView.cancelButtonIndex) + { + NNCancel(natnegCookie); + } + break; + + case ALERT_CONNECTING: + NSLog(@"ALERT_CONNECTING"); + if (buttonExists(alertView.cancelButtonIndex) && buttonIndex == alertView.cancelButtonIndex) + { + if (gt2Connection) + { + gt2CloseConnection(gt2Connection); + gt2Connection = NULL; + } + + if (gt2Socket != NULL) + { + GT2Socket tempsocket = gt2Socket; + gt2Socket = NULL; // prevent recursion + NSLog(@"ALERT_CONNECTING: null & close gt2 socket"); + gt2CloseSocket(tempsocket); + } + } + break; + + case ALERT_REPLAY: + NSLog(@"ALERT_REPLAY"); + if (buttonExists(alertView.cancelButtonIndex) && buttonIndex == alertView.cancelButtonIndex) + { + char buffer[32]; + int rcode = gtEncode(MSG_REPLAY_CANCELED_TYPE, MSG_REPLAY_CANCELED, buffer, sizeof(buffer)); + assert(rcode != -1); + NSLog(@"Protocol send MSG_REPLAY_CANCELED_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + } + break; + + case ALERT_REPLAY_CONFIRM: + NSLog(@"ALERT_REPLAY_CONFIRM"); + if (buttonExists(alertView.cancelButtonIndex) && buttonIndex == alertView.cancelButtonIndex) + { + char buffer[32]; + int rcode = gtEncode(MSG_REPLAY_CANCELED_TYPE, MSG_REPLAY_CANCELED, buffer, sizeof(buffer)); + assert(rcode != -1); + NSLog(@"Protocol send MSG_REPLAY_CANCELED_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + } + else if (buttonExists(alertView.firstOtherButtonIndex) >= 0 && buttonIndex == alertView.firstOtherButtonIndex) + { + char buffer[32]; + int rcode = gtEncode(MSG_REPLAY_TYPE, MSG_REPLAY, buffer, sizeof(buffer)); + assert(rcode != -1); + NSLog(@"Protocol send MSG_REPLAY_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + [self setupMatch: hosting]; + [navController popToViewController: gameController animated: YES]; + } + break; + + case ALERT_WAITING_FOR_LEADERS: + NSLog(@"ALERT_WAITING_FOR_LEADERS"); + /*if (buttonExists(alertView.cancelButtonIndex) && buttonIndex == alertView.cancelButtonIndex) + { + // what can we do to cancel a sake search? + // nothing for now. + }*/ + break; + + + case ALERT_NO_NETWORK_CONNECTION: + NSLog(@"ALERT_NO_NETWORK_CONNECTION"); + if (buttonExists(alertView.cancelButtonIndex) && buttonIndex == alertView.cancelButtonIndex) + { + NSLog(@"No network connection"); + //exit(0); + [self logout]; + } + break; + + default: + if( currentAlert == ALERT_NONE ) + NSLog(@"ALERT_NONE"); + else + NSLog(@"ERROR: UNKNOWN ALERT"); + break; + } + + currentAlert = ALERT_NONE; +} + +//- (UIViewController*)pushNewControllerOfType: (Class)type nib: (NSString*)nibName +//{ +// return [self pushNewControllerOfType: type nib: nibName animated: YES]; +//} + +- (UIViewController*)pushNewControllerOfType: (Class)type nib: (NSString*)nibName animated: (BOOL)animated +{ + UIViewController* viewController = [type alloc]; + [viewController initWithNibName: nibName bundle: nil]; + [navController pushViewController: viewController animated: animated]; + [viewController release]; + return viewController; +} + + +- (void)setupMatch: (bool)hostMatch +{ + start = 0; + countdown = 0; + localTime = 0; + remoteTime = 0; + racing = false; + reportSent = gsi_false; + hosting = hostMatch; +} + + +- (bool)setupHosting +{ + [self setupMatch: true]; + + + if( gt2Socket == NULL ) + { + NSLog(@"setupHosting: create gt2 socket"); + GT2Result gt2Result = gt2CreateSocket(>2Socket, HOST_PORT_STRING, 0, 0, SocketErrorCallback); + NSLog(@"gt2socket = %d", gt2Socket ); + if (gt2Result != GT2Success) + { + NSLog(@"gt2socket creation failed"); + hosting = false; + return false; + } + } + gt2SetSocketData(gt2Socket, self); + gt2Listen(gt2Socket, ConnectAttemptCallback); + gt2SetUnrecognizedMessageCallback(gt2Socket, UnrecognizedMessageCallback); + + + state = HOST_LISTENING; + NSLog(@"setupHosting state=HOST_LISTENING"); + + //qr2_error_t qr2Result = qr2_init( NULL, NULL, HOST_PORT /*2*/, SCRACE_GAMENAME, SCRACE_SECRETKEY, TRUE, TRUE, + // ServerKeyCallback, PlayerKeyCallback, TeamKeyCallback, KeyListCallback, CountCallback, AddErrorCallback, self); + + qr2_error_t qr2Result = qr2_init_socketA(NULL, gt2GetSocketSOCKET(gt2Socket), HOST_PORT, SCRACE_GAMENAME, SCRACE_SECRETKEY, TRUE, TRUE, ServerKeyCallback, PlayerKeyCallback, TeamKeyCallback, KeyListCallback, CountCallback, AddErrorCallback, self); + + if (qr2Result != e_qrnoerror) + { + if (gt2Connection) + { + gt2CloseConnection(gt2Connection); + gt2Connection = NULL; + } + if( gt2Socket ) + { + GT2Socket tempsocket = gt2Socket; + gt2Socket = NULL; // prevent recursion + NSLog(@"setupHosting: null & close gt2 socket"); + gt2CloseSocket(tempsocket); + } + + return false; + } + + qr2_register_natneg_callback(NULL, NatNegCallback); + + alertView = [[UIAlertView alloc] initWithTitle: @"Waiting for player" message: @"30 seconds" delegate: self cancelButtonTitle: nil otherButtonTitles: nil]; + [alertView show]; + currentAlert = ALERT_LISTENING; + + NSLog(@"setupHosting starts hostingCountdownTimer"); + [self startHostingCountdownTimer]; + + return true; +} + +- (bool)setupJoining: (unsigned int)ip port: (unsigned short)port useNatNeg: (bool)useNatNeg; +{ + [self setupMatch: false]; + + // create socket if new match + if( gt2Socket == NULL ) + { + NSLog(@"setupJoining: create gt2 socket"); + GT2Result result = gt2CreateSocket(>2Socket, CLIENT_PORT_STRING, 0, 0, SocketErrorCallback); + if (result != GT2Success) + return false; + } + + gt2SetSocketData(gt2Socket, self); + + // Do NAT negotiation if needed + if (useNatNeg) + { + const char* ip_addr = inet_ntoa((in_addr){ ip }); + + while (natnegCookie == 0) + { + natnegCookie = rand(); + } + + SBError sbret = ServerBrowserSendNatNegotiateCookieToServerA(serverBrowser, ip_addr, port, natnegCookie); + + if (sbret != sbe_noerror) + { + // What if somebody else is already doing a transaction with this cookie value? Astronomically improbable, but does the server return an error value? + NSLog(@"NN transaction cancelled due to cookie in use."); + return false; + } + + NegotiateError error = NNStartNatDetection(NNDetectionCallback); + + + gt2SetUnrecognizedMessageCallback(gt2Socket, UnrecognizedMessageCallback); + NegotiateError nnret = NNBeginNegotiationWithSocket(gt2GetSocketSOCKET(gt2Socket), natnegCookie, NN_CLIENT, NNProgressCallback, NNCompletedCallback, self); + + if (nnret != ne_noerror) + { + // ne_noerror, ne_allocerror, ne_socketerror, ne_dnserror + /*if( nnret == ne_socketerror ) + NSLog(@"NN socket error."); + else if( nnret == ne_dnserror ) + NSLog(@"NN DNS error."); + else + NSLog(@"NN allocation error."); + */ + return false; + } + + alertView = [[UIAlertView alloc] initWithTitle: @"Connection" message: @"Negotiating..." delegate: self cancelButtonTitle: nil otherButtonTitles: nil]; + [alertView show]; + currentAlert = ALERT_NATNEG; + } + + else // if not using NatNeg + { + NSString* remoteHost = [NSString stringWithFormat: @"%s:%d", inet_ntoa((in_addr){ ip }), port]; + [self startMultiPlayerGame: [remoteHost cStringUsingEncoding: NSASCIIStringEncoding]]; + } + + return true; +} + +- (void)onLoadRemoteDataTimer: (NSTimer*)timer +{ + gsCoreThink(0); +} + +- (void)onThinkTimer: (NSTimer*)timer +{ + static bool thinking; + + if (!thinking) + { + thinking = true; + + // Think so SDKs can process + ///////////////////////////////////////////// + if (gt2Socket != NULL) + gt2Think(gt2Socket); + + ghttpThink(); + scThink(interface); + + if (hosting) + qr2_think(NULL); + + if (serverBrowser != NULL) + ServerBrowserThink(serverBrowser); + + NNThink(); + + switch (state) + { + // ********************************************************** // + // ************************* HOST LOGIC ********************* // + // ********************************************************** // + + case HOST_LISTENING: + // Wait for client to join + ///////////////////////////////////////////// + break; + + case HOST_EXCHANGE_CERT: + { + char buffer[520]; + char cert[512]; + int rcode; + gsi_u32 certLen; + + // Store cert in a binary buffer for easy exchange + ///////////////////////////////////////////// + wsLoginCertWriteBinary(&gPlayerData.certificate, cert, sizeof(cert), &certLen); + + // Exchange certificates with the other player to validate (step 1) + ///////////////////////////////////////////// + rcode = gtEncode(MSG_TO_CLIENT_CERT_TYPE, MSG_TO_CLIENT_CERT, buffer, sizeof(buffer), gPlayerData.profileId, cert, certLen); + assert(rcode != -1); + NSLog(@"Protocol send MSG_TO_CLIENT_CERT_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + + // Wait for a reply + ///////////////////////////////////////////// + state = HOST_WAITING; + NSLog(@"onThinkTimer state=HOST_WAITING from HOST_EXCHANGE_CERT"); + break; + } + + case HOST_VERIFY_CERT: + // Validate authentication certificates (step 2) + ///////////////////////////////////////////// + remoteCertificate.mIsValid = wsLoginCertIsValid(&remoteCertificate); + if (gsi_is_false(remoteCertificate.mIsValid)) + { + [alertView dismissWithClickedButtonIndex: alertView.cancelButtonIndex animated: YES]; + [alertView release]; + alertView = nil; + MessageBox(@"Remote player has an invalid certificate, cancelling game."); + } + else + { + char buffer[32]; + int rcode = gtEncode(MSG_TO_CLIENT_CERTVERIFIED_TYPE, MSG_TO_CLIENT_CERTVERIFIED, buffer, sizeof(buffer)); + assert(rcode != -1); + NSLog(@"Protocol send MSG_TO_CLIENT_CERTVERIFIED_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + + // Wait for a reply + ///////////////////////////////////////////// + state = HOST_WAITING; + NSLog(@"onThinkTimer state=HOST_WAITING from HOST_VERIFY_CERT"); + } + break; + + case HOST_EXCHANGE_KEYS: + { + char buffer[512]; + int rcode; + SCPeerKeyExchangeMsg exchangeMsg; + + // P2P encryption exchange keys (step 3) + ///////////////////////////////////////////// + + // Each player should create a key for receiving data from the remote player + // For extra security, we use a different encryption key for each channel + ///////////////////////////////////////////// + scPeerCipherInit(&gPlayerData.certificate, &gPlayerData.peerRecvCipher); + + // Create a key exchange message for transmitting the key to the other player + // using the remote player's certificate to encrypt the cipher + ///////////////////////////////////////////// + scPeerCipherCreateKeyExchangeMsg(&remoteCertificate, &gPlayerData.peerRecvCipher, exchangeMsg); + + // Now send the key to the other player + ///////////////////////////////////////////// + rcode = gtEncode(MSG_TO_CLIENT_KEYS_TYPE, MSG_TO_CLIENT_KEYS, buffer, sizeof(buffer), exchangeMsg, GS_CRYPT_RSA_BYTE_SIZE); + assert(rcode != -1); + NSLog(@"Protocol send MSG_TO_CLIENT_KEYS_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + + // Wait for a reply + ///////////////////////////////////////////// + state = HOST_WAITING; + NSLog(@"onThinkTimer state=HOST_WAITING from HOST_EXCHANGE_KEYS"); + break; + } + + case HOST_CONNECTED: + if (alertView != nil) + { + //[alertView dismissWithClickedButtonIndex: -1 animated: YES]; + [alertView dismissWithClickedButtonIndex: alertView.firstOtherButtonIndex animated: YES]; + [alertView release]; + alertView = nil; + } + + state = RACING; + NSLog(@"onThinkTimer state=RACING on HOST_CONNECTED"); + + gameController = (GameController*)[self pushNewControllerOfType: [GameController class] nib: @"twoplayergame"]; + [gameController setRemoteNick: [NSString stringWithCString: remoteCertificate.mUniqueNick encoding: NSASCIIStringEncoding]]; + gOpponentData.profileId = remoteCertificate.mProfileId; + localTime = 0; + remoteTime = 0; + break; + + case HOST_SEND_SESSID: + if(sessionCreated) + { + int rcode; + char buffer[256]; + char sessionCrypt[SC_SESSION_GUID_SIZE]; + char connCrypt[SC_CONNECTION_GUID_SIZE]; + + // Encrypt the connID/session ID to send using P2P encryption + ///////////////////////////////////////////////////////// + memcpy(sessionCrypt, gPlayerData.sessionId, SC_SESSION_GUID_SIZE); + scPeerCipherEncryptBufferIV(&gPlayerData.peerSendCipher, 1, (gsi_u8*)sessionCrypt, SC_SESSION_GUID_SIZE); + + memcpy(connCrypt, gPlayerData.connectionId, SC_CONNECTION_GUID_SIZE); + scPeerCipherEncryptBufferIV(&gPlayerData.peerSendCipher, 2, (gsi_u8*)connCrypt, SC_CONNECTION_GUID_SIZE); + + OutputDebugString([NSString stringWithFormat: @"[HOST_SEND_SESSID] sessionCrypt: %.40s\n", sessionCrypt]); + OutputDebugString([NSString stringWithFormat: @"[HOST_SEND_SESSID] connCrypt: %.40s\n", connCrypt]); + + + // Now the host sends the session ID & his conn ID to the client + ///////////////////////////////////////////// + rcode = gtEncode(MSG_SESSION_ID_TYPE, MSG_SESSION_ID, buffer, sizeof(buffer), + sessionCrypt, SC_SESSION_GUID_SIZE, connCrypt, SC_CONNECTION_GUID_SIZE); + assert(rcode != -1); + NSLog(@"Protocol send MSG_SESSION_ID_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + + + // Once session is created, set the session ID and report intention + ///////////////////////////////////////////// + scSetSessionId(interface, gPlayerData.sessionId); + scSetReportIntention(interface, gPlayerData.connectionId, SCRACE_AUTHORITATIVE, &gPlayerData.certificate, &gPlayerData.privateData, + SetReportIntentionCallback, SCRACE_TIMEOUT_MS, self); + + sessionCreated = gsi_false; + + // Go back to racing state + ///////////////////////////////////////////// + state = RACING; + NSLog(@"onThinkTimer state=RACING on HOST_SEND_SESSID"); + } + break; + + case HOST_ERROR: + if (alertView != nil) + { + [alertView dismissWithClickedButtonIndex: alertView.cancelButtonIndex animated: YES]; + [alertView release]; + alertView = nil; + } + + MessageBox(@"Error setting up hosting"); + state = JOIN_CHOOSESERVER; + NSLog(@"onThinkTimer state=JOIN_CHOOSESERVER on HOST_ERROR"); + break; + + + // ********************************************************** // + // **************** JOIN (CLIENT) LOGIC ********************* // + // ********************************************************** // + + case JOIN_EXCHANGE_CERT: + // Wait for host to send cert first + ///////////////////////////////////////////// + break; + + case JOIN_VERIFY_CERT: + // Validate authentication certificates (step 2) + ///////////////////////////////////////////// + remoteCertificate.mIsValid = wsLoginCertIsValid(&remoteCertificate); + if (gsi_is_false(remoteCertificate.mIsValid)) + { + [alertView dismissWithClickedButtonIndex: alertView.cancelButtonIndex animated: YES]; + [alertView release]; + alertView = nil; + MessageBox(@"Remote player has an invalid certificate, cancelling game."); + [self logout]; + } + else + { + state = JOIN_EXCHANGE_KEYS; + NSLog(@"onThinkTimer state=JOIN_EXCHANGE_KEYS on JOIN_VERIFY_CERT"); + } + break; + + case JOIN_EXCHANGE_KEYS: + // Wait for host to send keys first + ///////////////////////////////////////////// + break; + + case JOIN_CONNECTED: + if (alertView != nil) + { + [alertView dismissWithClickedButtonIndex: -1 animated: YES]; + //[alertView dismissWithClickedButtonIndex: alertView.cancelButtonIndex animated: YES]; + [alertView release]; + alertView = nil; + } + state = RACING; + NSLog(@"onThinkTimer state=RACING on JOIN_CONNECTED"); + + gameController = (GameController*)[self pushNewControllerOfType: [GameController class] nib: @"twoplayergame"]; + [gameController setRemoteNick: [NSString stringWithCString: remoteCertificate.mUniqueNick encoding: NSASCIIStringEncoding]]; + localTime = 0; + remoteTime = 0; + break; + + case JOIN_SEND_CONNID: + // Once connection ID has been set, relay it to the host + ///////////////////////////////////////////// + if (connIdSet) + { + int rcode; + char buffer[128]; + char connCrypt[SC_CONNECTION_GUID_SIZE]; + + // Encrypt the connection ID to send using P2P encryption + ///////////////////////////////////////////////////////// + memcpy(connCrypt, gPlayerData.connectionId, SC_CONNECTION_GUID_SIZE); + scPeerCipherEncryptBufferIV(&gPlayerData.peerSendCipher, 1, (gsi_u8*)connCrypt, SC_CONNECTION_GUID_SIZE); + + OutputDebugString([NSString stringWithFormat: @"[JOIN_SEND_CONNID] connCrypt: %.40s\n", connCrypt]); + + + // Client needs to send the host his/her connection ID + ///////////////////////////////////////////// + rcode = gtEncode(MSG_CONNECTION_ID_TYPE, MSG_CONNECTION_ID, buffer, sizeof(buffer), connCrypt, SC_CONNECTION_GUID_SIZE); + assert(rcode != -1); + NSLog(@"Protocol send MSG_CONNECTION_ID_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + + connIdSet = gsi_false; + + // set profile id of opponent + gOpponentData.profileId = remoteCertificate.mProfileId; + + // Go back to the racing state + ///////////////////////////////////////////// + state = RACING; + NSLog(@"onThinkTimer state=RACING on JOIN_SEND_CONNID"); + } + break; + + case JOIN_ERROR: + if (alertView != nil) + { + [alertView dismissWithClickedButtonIndex: alertView.cancelButtonIndex animated: YES]; + [alertView release]; + alertView = nil; + } + + MessageBox(@"Error joining a game"); + state = JOIN_CHOOSESERVER; + NSLog(@"onThinkTimer state=JOIN_CHOOSESERVER on JOIN_ERROR"); + break; + } + + thinking = false; + } + + // Are we racing? + ///////////////// + if(racing) + { + // Did we finish? + ///////////////// + if(localTime) + { + // Let our opponent know we are done. + //////////////////////////////////////// + char buffer[64]; + int rcode; + int steps = gameController.numSteps; + if ( playerCount > 1 && steps != gameController.prevNumSteps ) + { + gameController.prevNumSteps = steps; + rcode = gtEncode(MSG_PROGRESS_TYPE, MSG_PROGRESS, buffer, sizeof(buffer), steps); + assert(rcode != -1); + NSLog(@"Protocol send MSG_PROGRESS_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2False); + } + + // Did we both finish? + ////////////////////// + if(remoteTime) + { + // Done racing. + /////////////// + racing = false; + state = FINISHED_RACE; + NSLog(@"onThinkTimer state=FINISHED_RACE"); + + [gameController doneRacing]; + + // Show the times. + ////////////////// + NSString* message; + if(localTime < remoteTime) + { + message = [NSString stringWithFormat: @"You won!\n%0.2fs to %0.2fs", localTime * absolute_to_seconds, remoteTime * absolute_to_seconds]; + win = gsi_true; + } + else if(remoteTime < localTime) + { + message = [NSString stringWithFormat: @"You lost!\n%0.2fs to %0.2fs", localTime * absolute_to_seconds, remoteTime * absolute_to_seconds]; + } + else + { + message = [NSString stringWithFormat: @"You tied!\n%0.2fs", localTime * absolute_to_seconds]; + tie = gsi_true; + } + + MessageBox(message); + + // Report the stats. + //////////////////// + if (!reportSent) + { + disconnected = gsi_false; + [self reportTwoPlayerStats]; + [self saveTwoPlayerGameStatsToFile]; + } + + GameResultsController* controller = (GameResultsController*)[self pushNewControllerOfType: [GameResultsController class] nib: @"twoplayerresults" animated: YES]; + int localTimeMillis = localTime * absolute_to_millis; + int remoteTimeMillis = remoteTime * absolute_to_millis; + //[controller setGameTime: std::max(localTimeMillis, remoteTimeMillis)]; + + [controller setLocalPlayer: gPlayerData.uniquenick time: localTimeMillis]; + [controller setRemotePlayer: [NSString stringWithCString: remoteCertificate.mUniqueNick encoding: NSASCIIStringEncoding] time: remoteTimeMillis]; + [controller setWinner ]; // call only after setting local & remote + [controller getImagesForResultsPage]; + } + } + else + { + char buffer[64]; + int rcode; + int steps = gameController.numSteps; + + // Let our opponent know how far we are if we tapped. + //////////////////////////////////////// + if ( playerCount > 1 && steps != gameController.prevNumSteps ) + { + gameController.prevNumSteps = steps; + rcode = gtEncode(MSG_PROGRESS_TYPE, MSG_PROGRESS, buffer, sizeof(buffer), steps); + assert(rcode != -1); + NSLog(@"Protocol send MSG_PROGRESS_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2False); + } + } + } +} + +- (void)onCountdownTimer: (NSTimer*)timer +{ + --countdown; + [self countdown]; + + if (countdown == 0) + { + [countdownTimer invalidate]; + countdownTimer = nil; + [self startRace]; + } +} + +// Countdown popup when 2 player game is connecting +- (void)onJoiningCountdownTimer: (NSTimer*)timer +{ + --joiningCountdown; + alertView.message = [NSString stringWithFormat: @"%d seconds", joiningCountdown]; + + if (joiningCountdown == 0) + { + [alertView dismissWithClickedButtonIndex: alertView.cancelButtonIndex animated: YES]; + [alertView release]; + alertView = nil; + + NSLog(@"onJoiningCountdownTimer stop timer"); + + [joiningCountdownTimer invalidate]; + joiningCountdownTimer = nil; + + if( currentAlert == ALERT_REPLAY ) + { + // tell opponent rematch is cancelled due to taking too long to reply + char buffer[32]; + int rcode = gtEncode(MSG_REPLAY_CANCELED_TYPE, MSG_REPLAY_CANCELED, buffer, sizeof(buffer)); + assert(rcode != -1); + NSLog(@"Protocol send MSG_REPLAY_CANCELED_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + + MessageBox(@"Failed to connect with remote player. Match canceled."); + } + else + { + NSLog( @"onJoiningCountdownTimer timed out with currentAlert=%d", currentAlert ); + MessageBox(@"Error establishing connection.", @"Connection Error"); + } + + // cancel hosting + MatchmakingController* matchmakingController = (MatchmakingController*)[self findExistingControllerOfType: [MatchmakingController class]]; + [ matchmakingController hostingCanceled ]; + + [self stopHostingServer]; + hosting = false; + racing = false; + state = JOIN_CHOOSESERVER; + NSLog(@"onJoiningCountdownTimer state=JOIN_CHOOSESERVER"); + + } +} + +- (void)onHostingCountdownTimer: (NSTimer*)timer +{ + --hostingCountdown; + alertView.message = [NSString stringWithFormat: @"%d seconds", hostingCountdown]; + + if (hostingCountdown == 0) + { + [alertView dismissWithClickedButtonIndex: alertView.cancelButtonIndex animated: YES]; + [alertView release]; + alertView = nil; + + [hostingCountdownTimer invalidate]; + hostingCountdownTimer = nil; + + // remove hosting from server + [self stopHostingServer]; + + //NSLog(@"onHostingCountdownTimer: qr2 shutdown"); + //qr2_shutdown(NULL); + + if( currentAlert == ALERT_REPLAY ) + { + // tell opponent rematch is cancelled due to taking too long to reply + char buffer[32]; + int rcode = gtEncode(MSG_REPLAY_CANCELED_TYPE, MSG_REPLAY_CANCELED, buffer, sizeof(buffer)); + assert(rcode != -1); + NSLog(@"Protocol send MSG_REPLAY_CANCELED_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + + MessageBox(@"Player has not responded. Rematch canceled."); + } + + // cancel hosting + MatchmakingController* matchmakingController = (MatchmakingController*)[self findExistingControllerOfType: [MatchmakingController class]]; + [ matchmakingController hostingCanceled ]; + + hosting = false; + racing = false; + state = JOIN_CHOOSESERVER; + NSLog(@"onHostingCountdownTimer state=JOIN_CHOOSESERVER"); + + [self returnToMatchMaking]; + } +} + +// Called after each 2 player matches +- (void)saveTwoPlayerGameStatsToFile +{ + // get my race time + int myRaceTimeMillis = (uint32_t)(localTime * absolute_to_millis); + + + if (gPlayerData.playerStatsData == nil) // no local stats file + { + // get here if no server stats also so init first ever stats + + // set win, loss, draw and disconnect + if (!disconnected) + { + gPlayerData.totalCompleteMatches = 1; + + gPlayerData.bestRaceTime = gPlayerData.worstRaceTime = + gPlayerData.totalRaceTime = myRaceTimeMillis; + gPlayerData.averageRaceTime = localTime; + + if( win ) + { + gPlayerData.careerWins = gPlayerData.matchWinStreak = + gPlayerData.careerLongestWinStreak = 1; + } + else if( !tie ) + { + gPlayerData.careerLosses = gPlayerData.matchLossStreak = + gPlayerData.careerLongestLossStreak = 1; + } + else + { + gPlayerData.careerDraws = gPlayerData.matchDrawStreak = + gPlayerData.careerLongestDrawStreak = 1; + } + + gPlayerData.disconnectRate = 0.0; + gPlayerData.careerDisconnects = 0; + } + else + { + gPlayerData.disconnectRate = 1.0; + gPlayerData.careerDisconnects = 1; + } + + gPlayerData.totalMatches = 1; + + // Save first 2 player game stats to new file + gPlayerData.playerStatsData = [[NSMutableDictionary alloc] initWithObjectsAndKeys: + [NSNumber numberWithInt: gPlayerData.bestRaceTime], matchBestTimeKey, + [NSNumber numberWithInt: gPlayerData.worstRaceTime], matchWorstTimeKey, + [NSNumber numberWithInt: (int)(gPlayerData.averageRaceTime * 1000)], matchAverageTimeKey, + + [NSNumber numberWithInt: gPlayerData.careerWins], matchWonKey, + [NSNumber numberWithInt: gPlayerData.careerLosses], matchLossKey, + [NSNumber numberWithInt: gPlayerData.careerDraws], matchDrawsKey, + + [NSNumber numberWithInt: gPlayerData.matchWinStreak], matchWinStreakKey, + [NSNumber numberWithInt: gPlayerData.matchLossStreak], matchLossStreakKey, + [NSNumber numberWithInt: gPlayerData.matchDrawStreak], matchDrawStreakKey, + + [NSNumber numberWithInt: gPlayerData.totalMatches], matchGamesPlayedKey, + [NSNumber numberWithInt: gPlayerData.totalRaceTime], matchTotalRaceTimeKey, + [NSNumber numberWithInt: gPlayerData.careerDisconnects], matchCareerDisconnectsKey, + [NSNumber numberWithInt: gPlayerData.totalCompleteMatches], matchTotalCompleteMatchesKey, + [NSNumber numberWithFloat: gPlayerData.disconnectRate], matchDisconnectRateKey, + + + [NSNumber numberWithInt: gPlayerData.careerLongestWinStreak], matchCareerLongestWinStreakKey, + [NSNumber numberWithInt: gPlayerData.careerLongestLossStreak], matchCareerLongestLossStreakKey, + [NSNumber numberWithInt: gPlayerData.careerLongestDrawStreak], matchCareerLongestDrawStreakKey, + + [NSString stringWithCString: gPlayerData.lastGameTime encoding: NSASCIIStringEncoding], lastTimePlayedKey, + nil]; + } + else if (disconnected) + { + int discon = getLocalIntegerStat(matchCareerDisconnectsKey); + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: discon+1] forKey: matchCareerDisconnectsKey]; + + int totalMatches = getLocalIntegerStat(matchGamesPlayedKey); + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: totalMatches+1] forKey: matchGamesPlayedKey]; + + [gPlayerData.playerStatsData setObject: [NSNumber numberWithFloat: 1.0*discon/totalMatches] forKey: matchDisconnectRateKey]; + + } + else + { + int bestRaceTime = getLocalIntegerStat(matchBestTimeKey); + if( myRaceTimeMillis < bestRaceTime ) + { + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: myRaceTimeMillis] forKey: matchBestTimeKey]; + } + + int worstRaceTime = getLocalIntegerStat(matchWorstTimeKey); + if( myRaceTimeMillis > worstRaceTime ) + { + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: myRaceTimeMillis] forKey: matchWorstTimeKey]; + } + + int totalRaceTime = getLocalIntegerStat(matchTotalRaceTimeKey) + myRaceTimeMillis; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: totalRaceTime] forKey: matchTotalRaceTimeKey]; + + int totalMatches = getLocalIntegerStat(matchGamesPlayedKey) + 1; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: totalMatches] forKey: matchGamesPlayedKey]; + + int discon = getLocalIntegerStat(matchCareerDisconnectsKey); + [gPlayerData.playerStatsData setObject: [NSNumber numberWithFloat: 1.0*discon/totalMatches] forKey: matchDisconnectRateKey]; + + int newAvgRaceTime = totalRaceTime / totalMatches; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithFloat: newAvgRaceTime] forKey: matchAverageTimeKey]; + + int totalCompletedMatches = getLocalIntegerStat(matchTotalCompleteMatchesKey) + 1; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: totalCompletedMatches] forKey: matchTotalCompleteMatchesKey]; + + //int wins = getLocalIntegerStat(matchWonKey) ; + //int loss = getLocalIntegerStat(matchLossKey); + //int draws = getLocalIntegerStat(matchDrawsKey); + + if( win ) + { + gPlayerData.careerWins++; + gPlayerData.matchWinStreak++; + gPlayerData.matchLossStreak = gPlayerData.matchDrawStreak = 0; + + if( gPlayerData.matchWinStreak > gPlayerData.careerLongestWinStreak ) + { + gPlayerData.careerLongestWinStreak = gPlayerData.matchWinStreak; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerLongestWinStreak] forKey: matchCareerLongestWinStreakKey]; + } + + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerWins] forKey: matchWonKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchWinStreak] forKey: matchWinStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchLossStreak] forKey: matchLossStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchDrawStreak] forKey: matchDrawStreakKey]; + } + else if( !tie ) + { + gPlayerData.careerLosses++; + gPlayerData.matchLossStreak++; + gPlayerData.matchWinStreak = gPlayerData.matchDrawStreak = 0; + + if( gPlayerData.matchLossStreak > gPlayerData.careerLongestLossStreak ) + { + gPlayerData.careerLongestLossStreak = gPlayerData.matchLossStreak; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerLongestLossStreak ] forKey: matchCareerLongestLossStreakKey]; + } + + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerLosses] forKey: matchLossKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchWinStreak ] forKey: matchWinStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchLossStreak ] forKey: matchLossStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchDrawStreak ] forKey: matchDrawStreakKey]; + } + else + { + gPlayerData.careerDraws++; + gPlayerData.matchDrawStreak++; + gPlayerData.matchWinStreak = gPlayerData.matchLossStreak = 0; + + if( gPlayerData.matchDrawStreak > gPlayerData.careerLongestDrawStreak ) + { + gPlayerData.careerLongestDrawStreak = gPlayerData.matchDrawStreak; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerLongestDrawStreak ] forKey: matchCareerLongestDrawStreakKey]; + } + + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.careerDraws ] forKey: matchDrawsKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchWinStreak ] forKey: matchWinStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchLossStreak ] forKey: matchLossStreakKey]; + [gPlayerData.playerStatsData setObject: [NSNumber numberWithInt: gPlayerData.matchDrawStreak ] forKey: matchDrawStreakKey]; + } + + [gPlayerData.playerStatsData setObject: [NSString stringWithCString: gPlayerData.lastGameTime encoding: NSASCIIStringEncoding] forKey: lastTimePlayedKey]; + } + + [gPlayerData.playerStatsData writeToFile: [gPlayerData.playerDataPath stringByAppendingPathComponent: playerStatsDataFile] atomically: YES]; + + // scReportAddFloatValue(statsReport, KEY_LONGITUDE, gPlayerData.playerLocation.coordinate.longitude); + // scReportAddFloatValue(statsReport, KEY_LATITUDE, gPlayerData.playerLocation.coordinate.latitude); + +} + + +- (void)reportTwoPlayerStats +{ + SCResult aResult = SCResult_NO_ERROR; + SCGameResult myGameResult, opponentGameResult; + int myTeam, opponentTeam; + int numTeams = SCRACE_NUM_TEAMS; + + + // Determine winners and losers + ///////////////////////////////////////////// + if (!disconnected) + { + if (win) + { + myGameResult = SCGameResult_WIN; + opponentGameResult = SCGameResult_LOSS; + } + else if (!tie) + { + myGameResult = SCGameResult_LOSS; + opponentGameResult = SCGameResult_WIN; + } + else + { + myGameResult = SCGameResult_DRAW; + opponentGameResult = SCGameResult_DRAW; + } + } + else + { + //report disconnected game - don't report any keys + myGameResult = SCGameResult_DISCONNECT; + opponentGameResult = SCGameResult_DISCONNECT; + } + + + // Determine teams, and who is on which + ///////////////////////////////////////////// + if (hosting) + { + myTeam = SCRACE_HOST_TEAM; + opponentTeam = SCRACE_CLIENT_TEAM; + } + else + { + myTeam = SCRACE_CLIENT_TEAM; + opponentTeam = SCRACE_HOST_TEAM; + } + + + // Create the report and begin building it + ///////////////////////////////////////////// + aResult = scCreateReport(interface, ATLAS_RULE_SET_VERSION, playerCount, numTeams, &statsReport); + if (aResult != SCResult_NO_ERROR) + { + MessageBox(@"Failed to Create Report - Out of memory"); + return; + } + + // Non-player data + ///////////////////////////////////////////// + scReportBeginGlobalData(statsReport); + // no global data reported + + // Player data + ///////////////////////////////////////////// + scReportBeginPlayerData(statsReport); + + // Report your data + //////////////////// + scReportBeginNewPlayer(statsReport); + scReportSetPlayerData(statsReport, PLAYER1, gPlayerData.connectionId, myTeam, + myGameResult, gPlayerData.profileId, &gPlayerData.certificate, gPlayerData.statsAuthdata); + if (!disconnected) + { + scReportAddIntValue(statsReport, KEY_MULTI_PLAY, (uint32_t)(TRUE)); + scReportAddIntValue(statsReport, RACE_TIME, (uint32_t)(localTime * absolute_to_millis)); + scReportAddFloatValue(statsReport, KEY_LONGITUDE, gPlayerData.playerLocation.coordinate.longitude); + scReportAddFloatValue(statsReport, KEY_LATITUDE, gPlayerData.playerLocation.coordinate.latitude); + + static char nickString[256]; + strcpy( nickString, [gPlayerData.uniquenick cStringUsingEncoding: NSASCIIStringEncoding]); + scReportAddStringValue(statsReport, KEY_NICK, nickString); + + // get current time and save as last game date/time + //gPlayerData.lastGameTime = [[NSDate date] description]; + //char raceDateTimeString[256]; + NSString *raceTimeString = [[NSDate date] description]; + strcpy( raceDateTimeString, [raceTimeString cStringUsingEncoding: NSASCIIStringEncoding] ); + gPlayerData.lastGameTime = raceDateTimeString; + scReportAddStringValue(statsReport, KEY_RACE_DATE_TIME, gPlayerData.lastGameTime ); + } + + // Report opponent data + //////////////////// + scReportBeginNewPlayer(statsReport); + scReportSetPlayerData(statsReport, PLAYER2, remoteConnId, opponentTeam, + opponentGameResult, remoteCertificate.mProfileId, &remoteCertificate, gPlayerData.statsAuthdata); + if (!disconnected) + scReportAddIntValue(statsReport, RACE_TIME, (uint32_t)(remoteTime * absolute_to_millis)); + + + // Team data + ///////////////////////////////////////////// + scReportBeginTeamData(statsReport); + + // Report your team data + //////////////////////// + scReportBeginNewTeam(statsReport); + scReportSetTeamData(statsReport, myTeam, myGameResult); + + // Report opponent team data + //////////////////////// + scReportBeginNewTeam(statsReport); + scReportSetTeamData(statsReport, opponentTeam, opponentGameResult); + + + // End the report and set GameStatus + if (!disconnected) + scReportEnd(statsReport, SCRACE_AUTHORITATIVE, SCGameStatus_COMPLETE); + else + scReportEnd(statsReport, SCRACE_AUTHORITATIVE, SCGameStatus_BROKEN); + + // Submit the Report + ///////////////////////////////////////////// + if (SCResult_NO_ERROR != scSubmitReport(interface, statsReport, SCRACE_AUTHORITATIVE, &gPlayerData.certificate, + &gPlayerData.privateData, SubmitReportCallback, SCRACE_TIMEOUT_MS, self)) + { + MessageBox(@"Failed to submit Stats Report."); + return; + } +} + + +- (void)reportSinglePlayerStats +{ + SCResult aResult = SCResult_NO_ERROR; +// SCGameResult myGameResult, opponentGameResult; +// int myTeam; +// int numTeams = SCRACE_NUM_TEAMS; + + + // Create the report and begin building it + ///////////////////////////////////////////// + aResult = scCreateReport(interface, ATLAS_RULE_SET_VERSION, 1, 0, &statsReport); + if (aResult != SCResult_NO_ERROR) + { + MessageBox(@"Failed to create Single Player Game stats report - Out of memory"); + return; + } + + scReportSetAsMatchless(statsReport); + + // Non-player data + ///////////////////////////////////////////// + scReportBeginGlobalData(statsReport); + // no global data reported + + // Player data + ///////////////////////////////////////////// + scReportBeginPlayerData(statsReport); + + // Report your data + //////////////////// + scReportBeginNewPlayer(statsReport); + scReportSetPlayerData(statsReport, PLAYER1, gPlayerData.connectionId, SCRACE_HOST_TEAM, SCGameResult_WIN, + gPlayerData.profileId, &gPlayerData.certificate, gPlayerData.statsAuthdata); + if (!disconnected) + { + scReportAddIntValue(statsReport, KEY_SINGLE_PLAY, (uint32_t)(TRUE)); + scReportAddIntValue(statsReport, SP_RACE_TIME, (uint32_t)(localTime * absolute_to_millis)); + scReportAddFloatValue(statsReport, KEY_LONGITUDE, gPlayerData.playerLocation.coordinate.longitude); + scReportAddFloatValue(statsReport, KEY_LATITUDE, gPlayerData.playerLocation.coordinate.latitude); + + static char nickString[256]; + strcpy( nickString, [gPlayerData.uniquenick cStringUsingEncoding: NSASCIIStringEncoding]); + scReportAddStringValue(statsReport, KEY_NICK, nickString); + + // get current time and save as last game date/time + //gPlayerData.lastGameTime = [[NSDate date] description]; + //char raceDateTimeString[256]; + //NSString *raceTimeString = [[NSDate date] description]; + //strcpy( raceDateTimeString, [raceTimeString cStringUsingEncoding: NSASCIIStringEncoding] ); + //gPlayerData.lastGameTime = raceDateTimeString; + scReportAddStringValue(statsReport, KEY_RACE_DATE_TIME, gPlayerData.lastGameTime ); + } + + // Team data + ///////////////////////////////////////////// + scReportBeginTeamData(statsReport); + + // End the report and set GameStatus + if (!disconnected) + scReportEnd(statsReport, gsi_false, SCGameStatus_COMPLETE); + else + scReportEnd(statsReport, gsi_false, SCGameStatus_BROKEN); + + // Submit the Report + ///////////////////////////////////////////// + if (SCResult_NO_ERROR != scSubmitReport(interface, statsReport, gsi_false, &gPlayerData.certificate, + &gPlayerData.privateData, SubmitReportCallback, SCRACE_TIMEOUT_MS, self)) + { + MessageBox(@"Failed to submit Single Player Game stats report."); + return; + } +} + + +- (void)countdown +{ + // If report was just recently submitted, reset the submission flag + /////////////////////////////////////////////////////////////////// + if (reportSent) + reportSent = gsi_false; + + if (hosting) + { + int rcode; + char message[32]; + + rcode = gtEncode(MSG_COUNTDOWN_TYPE, MSG_COUNTDOWN, message, sizeof(message), countdown); + assert(rcode != -1); + NSLog(@"Protocol send MSG_COUNTDOWN_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)message, rcode, GT2True); + } + + [gameController countdown: countdown]; +} + +- (void)hostingCountdown +{ +/* if (hosting) + { + alertView.message = @"qwe qwe"; + } +*/ + //[alertView countdown: countdown]; +} + +- (void)startRace +{ + if(hosting) + { + int rcode; + char buffer[32]; + rcode = gtEncode(MSG_START_RACE_TYPE, MSG_START_RACE, buffer, sizeof(buffer)); + assert(rcode != -1); + NSLog(@"Protocol send MSG_START_RACE_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + } + + // initialize stats markers + ///////////////////////////////////////////// + win = gsi_false; + tie = gsi_false; + disconnected = gsi_false; + + localTime = 0; + remoteTime = 0; + [gameController raceStarted]; + racing = true; + start = mach_absolute_time(); +} + +- (void)addErrorCallback: (qr2_error_t)error errorMessage: (const char*)errmsg +{ + if (alertView != nil) + { + [alertView dismissWithClickedButtonIndex: alertView.cancelButtonIndex animated: TRUE]; + [alertView release]; + alertView = nil; + } + + MessageBox([NSString stringWithCString: errmsg encoding: NSASCIIStringEncoding]); +} + +- (void)createSessionCallback: (const SCInterfacePtr)interfacePtr httpResult: (GHTTPResult)httpResult result: (SCResult)result +{ + if (httpResult == GHTTPSuccess && result == SCResult_NO_ERROR) + { + // Retrieve the Session/Connection ID to be used later + memcpy(gPlayerData.sessionId, scGetSessionId(interfacePtr), SC_SESSION_GUID_SIZE); + memcpy(gPlayerData.connectionId, scGetConnectionId(interfacePtr), SC_CONNECTION_GUID_SIZE); + + sessionCreated = gsi_true; + + OutputDebugString([NSString stringWithFormat: @"[createSessionCB] Session ID: %s\n", gPlayerData.sessionId]); + OutputDebugString([NSString stringWithFormat: @"[createSessionCB] Connection ID: [%s]\n", gPlayerData.connectionId]); + + if( playerCount == 1 ) + scSetReportIntention(interface, gPlayerData.connectionId, gsi_false, &gPlayerData.certificate, &gPlayerData.privateData, + SetReportIntentionCallback, SCRACE_TIMEOUT_MS, self); + } + else + { + OutputDebugString([NSString stringWithFormat: @"[createSessionCB] Error. HTTPResult: %d, SCResult: %d\n", httpResult, result]); + + [gameController stopRaceClock]; + MessageBox(@"Error Creating Stats Session"); + [self logout]; + } +} + +- (void)setReportIntentionCallback: (const SCInterfacePtr)interfacePtr httpResult: (GHTTPResult)httpResult result: (SCResult)result +{ + if (httpResult == GHTTPSuccess && result == SCResult_NO_ERROR) + { + // Retrieve the connection ID to be used later + ///////////////////////////////////////////////////////// + const char * connectionId = scGetConnectionId(interfacePtr); + memcpy(gPlayerData.connectionId, connectionId, SC_CONNECTION_GUID_SIZE); + connIdSet = gsi_true; + + OutputDebugString([NSString stringWithFormat: @"[setIntentionCB] Connection ID: [%s]\n", gPlayerData.connectionId]); + } + else + { + OutputDebugString([NSString stringWithFormat: @"[setIntentionCB] Error. HTTPResult: %d, SCResult: %d\n", httpResult, result]); + + MessageBox(@"Error initializing stats system. Login when network connection is up", @"Connection Error"); + [self logout]; + return; + } +} + +- (void)submitReportCallback: (const SCInterfacePtr)interface httpResult: (GHTTPResult)httpResult result: (SCResult)result +{ + if (httpResult != GHTTPSuccess || result != SCResult_NO_ERROR) + { + OutputDebugString([NSString stringWithFormat: @"[submitReportCB] Error. HTTPResult: %d, SCResult: %d\n", httpResult, result]); + + //MessageBox(@"Error submitting stats report.", @"Connection Error"); + //return; + } + + reportSent = gsi_true; //mark that we've submitted a report for this session + + // Cleanup + ///////////////////////////////////////////// + scDestroyReport(statsReport); + statsReport = NULL; +} + +- (void)getMyRecordsCallback: (SAKERequestResult) result inputData: (SAKEGetMyRecordsInput*)inputData outputData: (SAKEGetMyRecordsOutput*)outputData +{ + [alertView dismissWithClickedButtonIndex: -1 animated: YES]; + [alertView release]; + alertView = nil; + + [loadRemoteDataTimer invalidate]; + loadRemoteDataTimer = nil; + + if (result != SAKERequestResult_SUCCESS) + { + MessageBox(@"Attempt to retrieve data failed."); + return; + } + + if (outputData->mNumRecords == 0) + { + // New user. + gPlayerData.bestRaceTime = INITIAL_BEST_TIME; + gPlayerData.spBestRaceTime = INITIAL_BEST_TIME; + //gPlayerData.lastGameTime = '\0'; + return; + } + + // Assign vars: + SAKEField* fields = outputData->mRecords[0]; + + for (int n = 0; n < inputData->mNumFields; n++) { + int stat = ATLAS_GET_STAT(fields[n].mName); + + switch (stat) { + case CAREER_WINS: + gPlayerData.careerWins = fields[n].mValue.mInt; + break; + + case CAREER_LOSSES: + gPlayerData.careerLosses = fields[n].mValue.mInt; + break; + + case BEST_RACE_TIME: + gPlayerData.bestRaceTime = fields[n].mValue.mInt; + break; + + case WORST_RACE_TIME: + gPlayerData.worstRaceTime = fields[n].mValue.mInt; + break; + + case TOTAL_MATCHES: + gPlayerData.totalMatches = fields[n].mValue.mInt; + break; + + case AVERAGE_RACE_TIME: + gPlayerData.averageRaceTime = fields[n].mValue.mFloat; + break; + + case CURRENT_WIN_STREAK: + gPlayerData.matchWinStreak = fields[n].mValue.mInt; + break; + + case CURRENT_LOSS_STREAK: + gPlayerData.matchLossStreak = fields[n].mValue.mInt; + break; + + case TOTAL_RACE_TIME: + gPlayerData.totalRaceTime = fields[n].mValue.mInt; + break; + + case CAREER_DISCONNECTS: + gPlayerData.careerDisconnects = fields[n].mValue.mInt; + break; + + case DISCONNECT_RATE: + gPlayerData.disconnectRate = fields[n].mValue.mFloat; + break; + + case CAREER_DRAWS: + gPlayerData.careerDraws = fields[n].mValue.mInt; + break; + + case CURRENT_DRAW_STREAK: + gPlayerData.matchDrawStreak = fields[n].mValue.mInt; + break; + + case CAREER_LONGEST_WIN_STREAK: + gPlayerData.careerLongestWinStreak = fields[n].mValue.mInt; + break; + + case CAREER_LONGEST_LOSS_STREAK: + gPlayerData.careerLongestLossStreak = fields[n].mValue.mInt; + break; + + case CAREER_LONGEST_DRAW_STREAK: + gPlayerData.careerLongestDrawStreak = fields[n].mValue.mInt; + break; + + case TOTAL_COMPLETE_MATCHES: + gPlayerData.totalCompleteMatches = fields[n].mValue.mInt; + break; + + case LAST_TIME_PLAYED: + gPlayerData.lastGameTime = fields[n].mValue.mAsciiString; + break; + + case SP_AVERAGE_RACE_TIME: + gPlayerData.spAverageRaceTime = fields[n].mValue.mFloat; + break; + + case SP_BEST_RACE_TIME: + gPlayerData.spBestRaceTime = fields[n].mValue.mInt; + break; + + case SP_WORST_RACE_TIME: + gPlayerData.spWorstRaceTime = fields[n].mValue.mInt; + break; + + case SP_TOTAL_PLAYS: + gPlayerData.spTotalPlays = fields[n].mValue.mInt; + break; + + case SP_TOTAL_RACE_TIME: + gPlayerData.spTotalRaceTime = fields[n].mValue.mInt; + break; + } + } + + // Check if player stats from server are more up-to-date than the local stats saved on file + NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init]; + [dateFormatter setDateFormat:@"yyyy-MM-dd HH:mm:ss Z"]; + + NSString* aTime = (NSString*)[gPlayerData.playerStatsData objectForKey: lastTimePlayedKey ]; + + // if no stat timestamp is found in file, save server data to file + if( aTime == nil ) + { + [self savePlayerStatsToFile ]; + return; + } + + NSDate* fileDate = [dateFormatter dateFromString: aTime ]; + + aTime = [NSString stringWithCString: gPlayerData.lastGameTime encoding: NSASCIIStringEncoding]; + // if no stat timestamp is found from server, log and ignore + if( aTime == nil ) + { + NSLog(@"Stats from server does not have a timestamp."); + return; + } + NSDate* serverDate = [dateFormatter dateFromString: aTime]; + + NSComparisonResult dateResult = [ fileDate compare: serverDate ]; + if( dateResult == NSOrderedSame || dateResult == NSOrderedAscending ) + { + // File stats are older or same time as server stats + // Save server data to file + NSLog(@"Save server data to file"); + [self savePlayerStatsToFile ]; + } + else + { + // File stats are more up to date than server stats + // Use file data for display stats + NSLog(@"Use file data for display stats"); + } + +} + +- (void)connectedCallback: (GT2Connection)connection result: (GT2Result)result message: (GT2Byte*)message length: (int)length +{ + if (result == GT2Success) + { + state = JOIN_EXCHANGE_CERT; + NSLog(@"connectedCallback state=JOIN_EXCHANGE_CERT - (success)"); + } + else + { + state = JOIN_ERROR; + NSLog(@"connectedCallback state=JOIN_ERROR (fail)"); + gt2Connection = NULL; + } +} + +- (void)receivedCallback: (GT2Connection)connection message: (GT2Byte*)message length: (int)length reliable: (GT2Bool)reliable +{ + if(!message || !length) + return; + + switch (gtEncodedMessageType((char*)message)) + { + case MSG_COUNTDOWN_TYPE: + { + assert(!hosting); +NSLog(@"receivedCallback(MSG_COUNTDOWN_TYPE)"); + if(gtDecode(MSG_COUNTDOWN, (char*)message, length, &countdown) == -1) + { + state = JOIN_ERROR; + NSLog(@"state=JOIN_ERROR - MSG_COUNTDOWN_TYPE in receivedCallback"); + return; + } + + if( state == RACING ) + [self countdown]; + + break; + } + + case MSG_START_RACE_TYPE: +NSLog(@"receivedCallback(MSG_START_RACE_TYPE)"); + assert(!hosting); + [self startRace]; + break; + + case MSG_PROGRESS_TYPE: +NSLog(@"receivedCallback(MSG_PROGRESS_TYPE)"); + if(racing) + { + int progress; + + if(gtDecode(MSG_PROGRESS, (char*)message, length, &progress) != -1) + { + [gameController remoteProgress: progress]; + } + } + break; + + case MSG_END_RACE_TYPE: +NSLog(@"receivedCallback(MSG_END_RACE_TYPE)"); + if(racing) + { + uint32_t millis; + gtDecode(MSG_END_RACE, (char*)message, length, &millis); + remoteTime = millis * millis_to_absolute; + } + break; + + case MSG_TO_CLIENT_CERT_TYPE: + { +NSLog(@"receivedCallback(MSG_TO_CLIENT_CERT_TYPE)"); + char remoteCert[512]; + int remoteCertLen = 512; + int pid; + + if(gtDecode(MSG_TO_CLIENT_CERT, (char*)message, length, &pid, remoteCert, &remoteCertLen) == -1) + { + state = JOIN_ERROR; + NSLog(@"state=JOIN_ERROR - MSG_TO_CLIENT_CERT_TYPE in receivedCallback"); + return; + } + + // Parse the certificate + ///////////////////////////////////////////////////////// + wsLoginCertReadBinary(&remoteCertificate, remoteCert, remoteCertLen); + + // Build up and send the certificate response + ///////////////////////////////////////////////////////// + char buffer[520]; + char cert[512]; + int rcode; + gsi_u32 certLen; + + wsLoginCertWriteBinary(&gPlayerData.certificate, cert, sizeof(cert), &certLen); + + rcode = gtEncode(MSG_TO_HOST_CERT_TYPE, MSG_TO_HOST_CERT, buffer, sizeof(buffer), gPlayerData.profileId, cert, certLen); + assert(rcode != -1); + NSLog(@"Protocol send MSG_TO_HOST_CERT_TYPE\n"); + gt2Send(connection, (const unsigned char*)buffer, rcode, GT2True); + + state = JOIN_VERIFY_CERT; + NSLog(@"receivedCallback state=JOIN_VERIFY_CERT"); + break; + } + + case MSG_TO_HOST_CERT_TYPE: + { +NSLog(@"receivedCallback(MSG_TO_HOST_CERT_TYPE)"); + char remoteCert[512]; + int remoteCertLen = 512; + int pid; + + if(gtDecode(MSG_TO_HOST_CERT, (char*)message, length, &pid, remoteCert, &remoteCertLen) == -1) + { + state = HOST_ERROR; + NSLog(@"MSG_TO_HOST_CERT_TYPE - state=HOST_ERROR"); + return; + } + + // Parse the certificate + ///////////////////////////////////////////////////////// + wsLoginCertReadBinary(&remoteCertificate, remoteCert, remoteCertLen); + + state = HOST_VERIFY_CERT; + NSLog(@"state=JOIN_VERIFY_CERT"); + break; + } + + case MSG_TO_CLIENT_CERTVERIFIED_TYPE: +NSLog(@"receivedCallback(MSG_TO_CLIENT_CERTVERIFIED_TYPE)"); + char buffer[32]; + int rcode; + assert(!hosting); + rcode = gtEncode(MSG_TO_HOST_CERTVERIFIED_TYPE, MSG_TO_HOST_CERTVERIFIED, buffer, sizeof(buffer)); + assert(rcode != -1); + NSLog(@"Protocol send MSG_TO_HOST_CERTVERIFIED_TYPE\n"); + gt2Send(gt2Connection, (const unsigned char*)buffer, rcode, GT2True); + break; + + case MSG_TO_HOST_CERTVERIFIED_TYPE: +NSLog(@"receivedCallback(MSG_TO_HOST_CERTVERIFIED_TYPE)"); + assert(hosting); + state = HOST_EXCHANGE_KEYS; + NSLog(@"state=HOST_EXCHANGE_KEYS"); + break; + + case MSG_TO_CLIENT_KEYS_TYPE: + { +NSLog(@"receivedCallback(MSG_TO_CLIENT_KEYS_TYPE)"); + SCPeerKeyExchangeMsg recvMsg; + int recvMsgLen = GS_CRYPT_RSA_BYTE_SIZE; + + if(gtDecode(MSG_TO_CLIENT_KEYS, (char*)message, length, recvMsg, &recvMsgLen) == -1) + { + state = JOIN_ERROR; + NSLog(@"state=JOIN_ERROR - MSG_TO_CLIENT_KEYS_TYPE in receivedCallback"); + return; + } + + // Stop Join countdown + [self stopJoiningCountdownTimer]; // if running + + // Receiving player should parse the cipher key out of it. + // - decrypting the msg requires the local player's private data + ///////////////////////////////////////////////////////// + scPeerCipherParseKeyExchangeMsg(&gPlayerData.certificate, &gPlayerData.privateData, + recvMsg, &gPlayerData.peerSendCipher); + + + // Send response to host + ///////////////////////////////////////////////////////// + char buffer[512]; + int rcode; + SCPeerKeyExchangeMsg exchangeMsg; + + scPeerCipherInit(&gPlayerData.certificate, &gPlayerData.peerRecvCipher); + scPeerCipherCreateKeyExchangeMsg(&remoteCertificate, &gPlayerData.peerRecvCipher, exchangeMsg); + + // Now send the key to the other player + ///////////////////////////////////////////////////////// + rcode = gtEncode(MSG_TO_HOST_KEYS_TYPE, MSG_TO_HOST_KEYS, buffer, sizeof(buffer), exchangeMsg, GS_CRYPT_RSA_BYTE_SIZE); + assert(rcode != -1); + NSLog(@"Protocol send MSG_TO_HOST_KEYS_TYPE\n"); + gt2Send(connection, (const unsigned char*)buffer, rcode, GT2True); + + state = JOIN_CONNECTED; + NSLog(@"state=JOIN_CONNECTED"); + break; + } + + case MSG_TO_HOST_KEYS_TYPE: + { +NSLog(@"receivedCallback(MSG_TO_HOST_KEYS_TYPE)"); + SCPeerKeyExchangeMsg exchangeMsg; + int exchangeMsgLen = GS_CRYPT_RSA_BYTE_SIZE; + + if(gtDecode(MSG_TO_HOST_KEYS, (char*)message, length, exchangeMsg, &exchangeMsgLen) == -1) + { + state = HOST_ERROR; + NSLog(@"state=HOST_ERROR"); + return; + } + + // release hosting countdown + [self stopHostingCountdownTimer]; // if running + + // Receiving player should parse the cipher key out of it. + // - decrypting the msg requires the local player's private data + ///////////////////////////////////////////////////////// + scPeerCipherParseKeyExchangeMsg(&gPlayerData.certificate, &gPlayerData.privateData, + exchangeMsg, &gPlayerData.peerSendCipher); + + + state = HOST_CONNECTED; + NSLog(@"receivedCallback(MSG_TO_HOST_KEYS_TYPE) state=HOST_CONNECTED"); + + break; + } + + case MSG_SESSION_ID_TYPE: + { +NSLog(@"receivedCallback(MSG_SESSION_ID_TYPE)"); + assert(!hosting); + char sessionCrypt[SC_SESSION_GUID_SIZE]; + char connCrypt[SC_CONNECTION_GUID_SIZE]; + int sidLen = SC_SESSION_GUID_SIZE; + int ccidLen = SC_CONNECTION_GUID_SIZE; + + + // Client decodes sessionID / remote connID + ///////////////////////////////////////////////////////// + if(gtDecode(MSG_SESSION_ID, (char*)message, length, sessionCrypt, &sidLen, connCrypt, &ccidLen) == -1) + { + state = JOIN_ERROR; + NSLog(@"state=JOIN_ERROR - MSG_SESSION_ID_TYPE in receivedCallback"); + return; + } + + OutputDebugString([NSString stringWithFormat: @"[MSG_SESSION_ID_TYPE] sessionCrypt: %.40s\n", sessionCrypt]); + OutputDebugString([NSString stringWithFormat: @"[MSG_SESSION_ID_TYPE] connCrypt: %.40s\n", connCrypt]); + + // Decrypt the sessionID / remote connID + ///////////////////////////////////////////////////////// + scPeerCipherDecryptBufferIV(&gPlayerData.peerRecvCipher, 1, (gsi_u8*)sessionCrypt, SC_SESSION_GUID_SIZE); + memcpy(gPlayerData.sessionId, sessionCrypt, SC_SESSION_GUID_SIZE); + + scPeerCipherDecryptBufferIV(&gPlayerData.peerRecvCipher, 2, (gsi_u8*)connCrypt, SC_CONNECTION_GUID_SIZE); + memcpy(remoteConnId, connCrypt, SC_CONNECTION_GUID_SIZE); + + + OutputDebugString([NSString stringWithFormat: @"[MSG_SESSION_ID_TYPE] mSessionId: %s\n", gPlayerData.sessionId]); + OutputDebugString([NSString stringWithFormat: @"[MSG_SESSION_ID_TYPE] remoteConnId: %s\n", remoteConnId]); + + + // Joining player sets the session ID and the report intention + ///////////////////////////////////////////////////////// + scSetSessionId(gPlayerData.statsInterface, gPlayerData.sessionId); + scSetReportIntention(gPlayerData.statsInterface, gPlayerData.connectionId, SCRACE_AUTHORITATIVE, &gPlayerData.certificate, &gPlayerData.privateData, + SetReportIntentionCallback, SCRACE_TIMEOUT_MS, self); + + state = JOIN_SEND_CONNID; + NSLog(@"state=JOIN_SEND_CONNID"); + break; + } + + case MSG_CONNECTION_ID_TYPE: + { +NSLog(@"receivedCallback(MSG_CONNECTION_ID_TYPE)"); + assert(hosting); + char connCrypt[SC_CONNECTION_GUID_SIZE]; + int ccidLen = SC_CONNECTION_GUID_SIZE; + + // Hosting player decodes the remote conn ID for use in reporting + ///////////////////////////////////////////////////////// + if(gtDecode(MSG_CONNECTION_ID, (char*)message, length, connCrypt, &ccidLen) == -1) + { + state = HOST_ERROR; + NSLog(@"MSG_CONNECTION_ID_TYPE - state=HOST_ERROR"); + return; + } + + OutputDebugString([NSString stringWithFormat: @"[MSG_CONNECTION_ID_TYPE] connCrypt: %.40s\n", connCrypt]); + + + // Decrypt the remote conn ID + ///////////////////////////////////////////////////////// + scPeerCipherDecryptBufferIV(&gPlayerData.peerRecvCipher, 1, (gsi_u8*)connCrypt, SC_CONNECTION_GUID_SIZE); + memcpy(remoteConnId, connCrypt, SC_CONNECTION_GUID_SIZE); + + + OutputDebugString([NSString stringWithFormat: @"[MSG_CONNECTION_ID_TYPE] remoteConnId: %s\n", remoteConnId]); + + // at this point all of our exchanges are complete - ready for game start + ///////////////////////////////////////////////////////// + break; + } + + case MSG_REPLAY_TYPE: +NSLog(@"receivedCallback(MSG_REPLAY_TYPE) state=%d", state); + assert(state == FINISHED_RACE); + if (currentAlert == ALERT_REPLAY) + { + // Remote player agreed to a replay. + [alertView dismissWithClickedButtonIndex: -1 animated: YES]; + [alertView release]; + alertView = nil; + [self stopHostingCountdownTimer]; + + [self setupMatch: hosting]; + [navController popToViewController: gameController animated: YES]; + } + else + { + // Remote player asked for a replay. + alertView = [[UIAlertView alloc] initWithTitle: @"You have been challenged to a rematch!" message: @"Accept?" delegate: self cancelButtonTitle: @"No" otherButtonTitles: @"Yes", nil]; + [alertView show]; + currentAlert = ALERT_REPLAY_CONFIRM; + } + break; + + case MSG_REPLAY_CANCELED_TYPE: +NSLog(@"receivedCallback(MSG_REPLAY_CANCELED_TYPE)"); + [self stopHostingCountdownTimer]; + + assert(state == FINISHED_RACE); + if (currentAlert == ALERT_REPLAY) { + [alertView dismissWithClickedButtonIndex: -1 animated: YES]; + MessageBox(@"Your replay request was declined."); + } + else if (currentAlert == ALERT_REPLAY_CONFIRM) { + [alertView dismissWithClickedButtonIndex: -1 animated: YES]; + MessageBox(@"Your opponent canceled the replay request."); + } + } +} + +// Called when the connection to remote player is closed +- (void)closedCallback: (GT2Connection)connection reason: (GT2CloseReason)reason +{ + // Logout triggers this function when it's a local close + // so we need to make sure we don't loop + NSLog(@"closedCallback reason = %d state=%d", reason, state ); + + // Stop host countdown + [self stopHostingCountdownTimer]; + + if (reason != GT2LocalClose) + { + NSLog(@"closedCallback reason=%d - NOT GT2LocalClose", reason); + + if( state == RACING ) + { + // stop the game-start countdown timer + if (countdownTimer != nil) + { + [countdownTimer invalidate]; + countdownTimer = nil; + } + + // stop game race clock + [gameController stopRaceClock]; + + // If the connection was closed remotely, or connection errors occured + // we should report stats anyways to report disconnects + MessageBox(@"Opponent canceled match"); + + [self stopHostingServer]; + disconnected = gsi_true; + + [self reportTwoPlayerStats]; + [self saveTwoPlayerGameStatsToFile]; + + MatchmakingController* matchmakingController = (MatchmakingController*)[self findExistingControllerOfType: [MatchmakingController class]]; + [navController popToViewController: matchmakingController animated: YES]; + } + else if ( state == HOST_CONNECTED ) + { + [alertView dismissWithClickedButtonIndex: -1 animated: YES]; + [alertView release]; + alertView = nil; + + [self stopJoiningCountdownTimer]; + [self stopHostingServer]; + disconnected = gsi_true; + + [self reportTwoPlayerStats]; + [self saveTwoPlayerGameStatsToFile]; + + // set state so that onThinkTimer does not go to game page + state = JOIN_CHOOSESERVER; + + //MatchmakingController* matchmakingController = (MatchmakingController*)[self findExistingControllerOfType: [MatchmakingController class]]; + //[navController popToViewController: matchmakingController animated: YES]; + + MessageBox(@"Opponent canceled" ); + } + + else + { + NSLog(@"closedCallback currentAlert=%d - NOT GT2LocalClose", currentAlert); + + GameResultsController* resultsController = (GameResultsController*)[self findExistingControllerOfType: [GameResultsController class]]; + if (resultsController != nil) + { + [resultsController connectionDropped]; + } + + if ( state != FINISHED_RACE ) + { + switch (currentAlert) + { + case ALERT_REPLAY: + case ALERT_REPLAY_CONFIRM: + [alertView dismissWithClickedButtonIndex: -1 animated: YES]; + [alertView release]; + alertView = nil; + + [self stopHostingServer]; + disconnected = gsi_true; + + MessageBox(@"The connection to the other player was terminated."); + break; + + default: + // stop game race clock + [gameController stopRaceClock]; + + [alertView dismissWithClickedButtonIndex: -1 animated: YES]; + [alertView release]; + alertView = nil; + + [self stopHostingServer]; + disconnected = gsi_true; + + MessageBox(@"Opponent quit race" ); + [self returnToMatchMaking]; + break; + } + } + } + } + + + racing = false; + if (gt2Connection) + { + gt2CloseConnection(gt2Connection); + gt2Connection = NULL; + } + if (gt2Socket) + { + GT2Socket tempsocket = gt2Socket; + gt2Socket = NULL; // prevent recursion + NSLog(@"closeCallback: null & close gt2 socket"); + gt2CloseSocket(tempsocket); + } +} + +- (void)connectAttemptCallback: (GT2Socket)listener connection: (GT2Connection)connection ip: (unsigned int)ip port: (unsigned short)port latency: (int)latency message: (GT2Byte*)message length: (int)length +{ + NSLog(@"start connectAttemptCallback"); + + int pid = 0; + char nick[128]; + + // Only allow one connection. + ///////////////////////////// + if(gt2Connection) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Decode the pid. + ////////////////// + if(message && length) + { + if(gtDecodeNoType(MSG_CONNECT, (char*)message, length, &pid, nick) == -1) + pid = 0; + } + + // If we didn't/couldn't get the pid, reject the attempt. + ///////////////////////////////////////////////////////// + if(!pid) + { + gt2Reject(connection, NULL, 0); + return; + } + + // Accept the connection. + ///////////////////////// + GT2ConnectionCallbacks callbacks = { + NULL, + ReceivedCallback, + ClosedCallback, + NULL + }; + + if(!gt2Accept(connection, &callbacks)) + return; + + // Set some states. + /////////////////// + gt2Connection = connection; + gt2SetConnectionData(gt2Connection, self); + state = HOST_EXCHANGE_CERT; //once connected, exchange certifications + NSLog(@"connectAttemptCallback state=HOST_EXCHANGE_CERT"); + + if( hosting ) + { + // remove countdown pop-up + [alertView dismissWithClickedButtonIndex: 0 animated: YES]; + [alertView release]; + alertView = nil; + + // stop hosting countdown + [self stopHostingCountdownTimer]; + + // show that we are connecting + alertView = [[UIAlertView alloc] initWithTitle: @"Initiating race with player" message: @"30 seconds" delegate: self cancelButtonTitle: nil otherButtonTitles: nil]; + [alertView show]; + currentAlert = ALERT_CONNECTING; //NEW ??? + + [self startJoiningCountdownTimer]; + } + + connected = true; + qr2_send_statechanged(NULL); +} + + +// Called by NN SDK for host only +- (void)natnegCallback: (int)cookie +{ + if( hosting ) + { + // remove countdown pop-up +// [alertView dismissWithClickedButtonIndex: 0 animated: YES]; + [alertView dismissWithClickedButtonIndex: -1 animated: YES]; + [alertView release]; + alertView = nil; + + // stop hosting countdown + [self stopHostingCountdownTimer]; + + // show that we are connecting + alertView = [[UIAlertView alloc] initWithTitle: @"Connecting to player" message: @"30 seconds" delegate: self cancelButtonTitle: nil otherButtonTitles: nil]; + [alertView show]; + currentAlert = ALERT_CONNECTING; //NEW ??? + + [self startJoiningCountdownTimer]; + } + + NNBeginNegotiationWithSocket(gt2GetSocketSOCKET(gt2Socket), cookie, NN_SERVER, NNProgressCallback, NNCompletedCallback, self); +} + +- (void)natnegCompletedCallback: (NegotiateResult)result socket: (SOCKET)gamesocket remoteaddr: (sockaddr_in*)remoteaddr +{ + NSLog(@"natnegCompletedCallback start (state=%d) (result=%d)", state, result); + + if (state == HOST_LISTENING) + { + NSLog(@"natnegCompletedCallback host waiting for client\n"); + // Server side doesn't have to do anything here. + return; + } + + // Close the Connection popup didDismissWithButtonIndex + [alertView dismissWithClickedButtonIndex: -1 animated: YES]; + [alertView release]; + alertView = nil; + + if (result != nr_success) + { + switch (result) + { + case nr_inittimeout: + MessageBox(@"Unable to communicate with NAT Negotiation server."); + break; + + case nr_pingtimeout: + MessageBox(@"Couldn't establish a direct connection to the other player."); + break; + + case nr_deadbeatpartner: + MessageBox(@"The other player did not register with the NAT Negotiation Server."); + break; + + default: + MessageBox(@"An error occurred connecting to the remote player."); + break; + } + + return; + } + + NSString* remoteAddress = [NSString stringWithFormat: @"%s:%d", inet_ntoa(remoteaddr->sin_addr), ntohs(remoteaddr->sin_port)]; + NSLog(@"remote Address = %s", remoteAddress); + + [self startMultiPlayerGame: [remoteAddress cStringUsingEncoding: NSASCIIStringEncoding]]; +} + + +// System needs more memory +// Remove any data we can retrieve from server +- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application +{ + MessageBox(@"System is running low on memory. Logging out", @"Low memory"); + [self logout]; + NSLog( @"WARNING: applicationDidReceiveMemoryWarning" ); +} + +@end + + +void ServerKeyCallback(int keyid, qr2_buffer_t outbuf, void* userdata) +{ + switch (keyid) + { + case HOSTNAME_KEY: + qr2_buffer_addA(outbuf, [gPlayerData.uniquenick cStringUsingEncoding: NSASCIIStringEncoding]); + break; + + case GAMEVER_KEY: + qr2_buffer_addA(outbuf, SCRACE_VERSION_STR); + break; + + case HOSTPORT_KEY: + qr2_buffer_add_int(outbuf, HOST_PORT2); + break; + + case GAMEMODE_KEY: + { + switch([ appDelegate state ]) + { + RACING: + qr2_buffer_addA(outbuf, "closedplaying"); + break; + + HOST_CONNECTED: + JOIN_CONNECTED: + qr2_buffer_addA(outbuf, "closedwaiting"); // accept no more players + break; + + HOST_LISTENING: + JOIN_ERROR: + qr2_buffer_addA(outbuf, "openwaiting"); + break; + + default: + qr2_buffer_addA(outbuf, "openwaiting"); // hosting and waiting + break; + } + break; + } + + case TIMEELAPSED_KEY: + qr2_buffer_add_int(outbuf, 0); + break; + + case NUMPLAYERS_KEY: + qr2_buffer_add_int(outbuf, ( [appDelegate connected] ? 2 : 1) ); + break; + + case MAXPLAYERS_KEY: + qr2_buffer_add_int(outbuf, 2); + break; + + default: + qr2_buffer_addA(outbuf, ""); + break; + } +} + +void PlayerKeyCallback(int keyid, int index, qr2_buffer_t outbuf, void* userdata) +{ + switch (keyid) + { + case PLAYER__KEY: + qr2_buffer_addA(outbuf, [gPlayerData.uniquenick cStringUsingEncoding: NSASCIIStringEncoding]); + break; + + case PING__KEY: + qr2_buffer_add_int(outbuf, 0); + break; + + case PID__KEY: + qr2_buffer_add_int(outbuf, gPlayerData.profileId); + break; + + default: + qr2_buffer_addA(outbuf, ""); + break; + } +} + +void TeamKeyCallback(int keyid, int index, qr2_buffer_t outbuf, void* userdata) +{ + qr2_buffer_addA(outbuf, ""); +} + +int CountCallback(qr2_key_type keytype, void* userdata) +{ + if( keytype == key_player ) + return 1; + + return 0; +} + +void KeyListCallback(qr2_key_type keytype, qr2_keybuffer_t keybuffer, void* userdata) +{ + 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, GAMEMODE_KEY); + qr2_keybuffer_add(keybuffer, TIMEELAPSED_KEY); + qr2_keybuffer_add(keybuffer, NUMPLAYERS_KEY); + qr2_keybuffer_add(keybuffer, MAXPLAYERS_KEY); + break; + + case key_player: + qr2_keybuffer_add(keybuffer, PLAYER__KEY); + qr2_keybuffer_add(keybuffer, PING__KEY); + qr2_keybuffer_add(keybuffer, PID__KEY); + break; + + case key_team: + case key_type_count: + break; + } +} + +void AddErrorCallback(qr2_error_t error, char* errmsg, void* userdata) +{ + [(AppDelegate*)userdata addErrorCallback: error errorMessage: errmsg]; +} + +void NatNegCallback(int cookie, void* userData) +{ + [(AppDelegate*)userData natnegCallback: cookie]; +} + +void CreateSessionCallback(const SCInterfacePtr interface, GHTTPResult httpResult, SCResult result, void* userData) +{ + [(AppDelegate*)userData createSessionCallback: interface httpResult: httpResult result: result]; +} + +void SetReportIntentionCallback(const SCInterfacePtr interface, GHTTPResult httpResult, SCResult result, void* userData) +{ + [(AppDelegate*)userData setReportIntentionCallback: interface httpResult: httpResult result: result]; +} + +void SubmitReportCallback(const SCInterfacePtr interface, GHTTPResult httpResult, SCResult result, void* userData) +{ + [(AppDelegate*)userData submitReportCallback: interface httpResult: httpResult result: result]; +} + +void GetMyRecordsCallback(SAKE sake, SAKERequest request, SAKERequestResult result, SAKEGetMyRecordsInput* inputData, SAKEGetMyRecordsOutput* outputData, void* userData) +{ + [(AppDelegate*)userData getMyRecordsCallback: result inputData: inputData outputData: outputData]; +} + +void ConnectedCallback(GT2Connection connection, GT2Result result, GT2Byte* message, int len) +{ + AppDelegate* delegate = (AppDelegate*)gt2GetConnectionData(connection); + [delegate connectedCallback: connection result: result message: message length: len]; +} + +void ReceivedCallback(GT2Connection connection, GT2Byte* message, int len, GT2Bool reliable) +{ + AppDelegate* delegate = (AppDelegate*)gt2GetConnectionData(connection); + [delegate receivedCallback: connection message: message length: len reliable: reliable]; +} + +void ClosedCallback(GT2Connection connection, GT2CloseReason reason) +{ + AppDelegate* delegate = (AppDelegate*)gt2GetConnectionData(connection); + [delegate closedCallback: connection reason: reason]; +} + +void ConnectAttemptCallback(GT2Socket listener, GT2Connection connection, unsigned int ip, unsigned short port, int latency, GT2Byte* message, int len) +{ + AppDelegate* delegate = (AppDelegate*)gt2GetSocketData(listener); + [delegate connectAttemptCallback: listener connection: connection ip: ip port: port latency: latency message: message length: len]; +} + +void SocketErrorCallback(GT2Socket socket) +{ +} + +GT2Bool UnrecognizedMessageCallback(GT2Socket socket, unsigned int ip, unsigned short port, GT2Byte* message, int len) +{ + static GT2Byte qrHeader[] = { QR_MAGIC_1, QR_MAGIC_2 }; + static GT2Byte nnHeader[] = { NN_MAGIC_0, NN_MAGIC_1, NN_MAGIC_2, NN_MAGIC_3, NN_MAGIC_4, NN_MAGIC_5 }; + + if (memcmp(message, qrHeader, sizeof(qrHeader)) == 0) + { + sockaddr_in address = { sizeof(sockaddr_in), AF_INET, htons(port), { ip }, { 0 } }; + qr2_parse_queryA(NULL, (char*)message, len, (sockaddr*)&address); + return GT2True; + } + else if (memcmp(message, nnHeader, sizeof(nnHeader)) == 0) + { + sockaddr_in address = { sizeof(sockaddr_in), AF_INET, htons(port), { ip }, { 0 } }; + NNProcessData((char*)message, len, &address); + return GT2True; + } + + NSLog(@"UnrecognizedMessageCallback: unknown message type with header[%d, %d]", message[0], message[1]); + return GT2False; +} + +void NNDetectionCallback(gsi_bool success, NAT nat) +{ + int charsWritten; + char aString[256]; + + NSLog(@"****************************************************************************\n"); + if(success == gsi_false) + { + NSLog(@"NNDetectionCallback: Failed to fully detect the NAT. Please try the detection again.\n"); + return; + } + + memset(aString, 0, 255); + charsWritten = sprintf(aString, "NNDetectionCallback: The detected NAT is a "); + switch(nat.natType) + { + case no_nat: + charsWritten = sprintf(aString, "NNDetectionCallback: No NAT detected."); + break; + case firewall_only: + charsWritten = sprintf(aString, "NNDetectionCallback: No NAT detected, but firewall may be present."); + break; + case full_cone: + charsWritten += sprintf(aString + charsWritten, "full cone "); + break; + case restricted_cone: + charsWritten += sprintf(aString + charsWritten, "restricted cone "); + break; + case port_restricted_cone: + charsWritten += sprintf(aString + charsWritten, "port restricted cone "); + break; + case symmetric: + charsWritten += sprintf(aString + charsWritten, "symmetric "); + break; + case unknown: + default: + charsWritten = sprintf(aString, "NNDetectionCallback: Unknown NAT type detected "); + break; + } + + if(nat.natType != no_nat && nat.natType != firewall_only) + switch(nat.mappingScheme) + { + case private_as_public: + charsWritten += sprintf(aString + charsWritten, "and is using the private port as the public port."); + break; + case consistent_port: + charsWritten += sprintf(aString + charsWritten, "and is using the same public port for all requests from the same private port."); + break; + case incremental: + charsWritten += sprintf(aString + charsWritten, "and is using an incremental port mapping scheme."); + break; + case mixed: + charsWritten += sprintf(aString + charsWritten, "and is using a mixed port mapping scheme."); + break; + case unrecognized: + default: + charsWritten += sprintf(aString + charsWritten, "and is using an unrecognized port mapping scheme."); + break; + } + + charsWritten += sprintf(aString + charsWritten, "\n"); + + printf("NNDetectionCallback: [%s]", aString ); + gsDebugFormat(GSIDebugCat_App, GSIDebugType_Misc, GSIDebugLevel_Notice, aString); +} + + +void NNProgressCallback(NegotiateState state, void* userdata) +{ + NSLog(@"NN progress status: %d", (int) state ); +} + +void NNCompletedCallback(NegotiateResult result, SOCKET gamesocket, sockaddr_in* remoteaddr, void* userdata) +{ + AppDelegate* delegate = (AppDelegate*)userdata; + [delegate natnegCompletedCallback: result socket: gamesocket remoteaddr: remoteaddr]; +} + +// C callback bridges to Objective C +static void SearchForLeadersTimeRecordsCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData) +{ + [(AppDelegate*)userData searchForLeadersTimeRecordsCallback: sake request: request result: result inputData: inputData outputData: outputData]; +} + +static void SearchForTop5TimeRecordsCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData) +{ + [(AppDelegate*)userData searchForTop5TimeRecordsCallback: sake request: request result: result inputData: inputData outputData: outputData]; +} + +static void GetMyLeaderPositionCallback(SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData) +{ + [(AppDelegate*)userData getMyLeaderPositionCallback: sake request: request result: result inputData: inputData outputData: outputData]; +} diff --git a/Samples/TapRace/iphone/BuddyListCellView.h b/Samples/TapRace/iphone/BuddyListCellView.h new file mode 100644 index 00000000..09f2f0b9 --- /dev/null +++ b/Samples/TapRace/iphone/BuddyListCellView.h @@ -0,0 +1,42 @@ +// +// BuddyListCellView.h +// +// Created by CW Hicks 10/21/08. +// Copyright 2008 IGN. All rights reserved. +// + +#import + +@interface BuddyListCellView : UIView +{ + IBOutlet UIView* cellView; + IBOutlet UIImageView* thumbnail; + IBOutlet UILabel* nameLabel; + IBOutlet UILabel* indexLabel; + IBOutlet UILabel* timeLabel; + + int profileId; + unsigned int pictureFileId; + int index; + + int cannotDie; // ref count for number of outstanding callbacks + bool mustDie; + + IBOutlet UIActivityIndicatorView* busy; +} + +@property (nonatomic, retain) UIView * cellView; +@property int profileId; +@property unsigned int pictureFileId; +@property int index; + +- (void)setName: (NSString*)name; +- (void)setIndex: (int)newIndex; +- (void)setImage: (UIImage*)image; +- (void)setTime: (int)newTime; + +- (id)initCellView; // call to load from xib +- (void)getImageFromNetwork; + +@end + diff --git a/Samples/TapRace/iphone/BuddyListCellView.mm b/Samples/TapRace/iphone/BuddyListCellView.mm new file mode 100644 index 00000000..bdd4fad7 --- /dev/null +++ b/Samples/TapRace/iphone/BuddyListCellView.mm @@ -0,0 +1,280 @@ +// +// BuddyListCellView.mm +// +// Created by CW Hicks on 10/21/08. +// Copyright 2008 IGN. All rights reserved. +// + +#import "BuddyListCellView.h" +#import "Utility.h" +#import "AppDelegate.h" +#import "sake/sake.h" + +void SakeSearchForBuddyRecordsCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ); +GHTTPBool DownloadBuddyImageCompletedCallback(GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param); + +@implementation BuddyListCellView + +@synthesize cellView; +@synthesize profileId; +@synthesize pictureFileId; +@synthesize index; + +- (id) initCellView +{ + [[NSBundle mainBundle] loadNibNamed: @"buddyListCell" owner: self options: nil]; + return self; +} + + +- (id)initWithFrame:(CGRect)frame +{ + if ( (self = [super initWithFrame:frame]) ) + { + // Initialization code + } + return self; +} + +- (void)awakeFromNib +{ +} + +- (void)setSelected:(BOOL)selected animated:(BOOL)animated +{ + // Configure the view for the selected state +} + + +- (void)dealloc +{ + if (cannotDie > 0) + { + // this must be preserved until the last callback returns + mustDie = YES; + [self retain]; // bump the ref count so won't be released + return; + } + + [cellView release]; + [thumbnail release]; + [nameLabel release]; + [indexLabel release]; + [timeLabel release]; + [busy release]; + + [super dealloc]; +} + +- (void)setName: (NSString*)name +{ + nameLabel.text = [name copy]; +} + +- (void)setIndex: (int)newIndex +{ + index = newIndex-1; + indexLabel.text = [NSString stringWithFormat: @"%d", newIndex]; +} + +- (void)setImage: (UIImage*)image +{ + [thumbnail setImage:image]; +} + +- (void)setTime: (int)newTime +{ + SetTimeLabel( timeLabel, newTime ); +} + +- (void) drawThumbnailFromImage: (UIImage*)image +{ + LeaderboardObject* leader = (LeaderboardObject*)[gLeaderboardData objectAtIndex: index]; + + // resize the image to represent the profile size and get the current colorspace + int imgh = image.size.height; + int imgw = image.size.width; + CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB( ); + void * data = calloc(imgh, 4*imgw); + CGContextRef thumbnailContextRef = CGBitmapContextCreate ( data, // void *data, + imgw, // size_t width, + imgh, // size_t height, + 8, // size_t bitsPerComponent, + 4*imgw, // size_t bytesPerRow, + colorspace, // CGColorSpaceRef colorspace, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big // CGBitmapInfo bitmapInfo + ); + CGRect tnrect = CGRectMake( 0, 0, imgw, imgh ); + CGContextDrawImage ( thumbnailContextRef, tnrect, image.CGImage ); // draw (and resize) image + + + if (leader.thumbNail != nil) + [leader.thumbNail release]; + + NSLog(@"setting thumbnail for player[%d] = %d\n", index, leader.profileId); + + // set thumbnail for leader + leader.thumbNail = [[UIImage imageWithCGImage: CGBitmapContextCreateImage ( thumbnailContextRef )] retain]; + + CGContextRelease(thumbnailContextRef); + CGColorSpaceRelease(colorspace); + free(data); + + [busy stopAnimating]; + busy.hidden=YES; + [thumbnail setImage: leader.thumbNail]; + [thumbnail setNeedsDisplay]; +} + +- (GHTTPBool) downloadCompletedCallbackRequest: (GHTTPRequest) request result: (GHTTPResult) result buffer: (char *) buffer bufferLen: (GHTTPByteCount) bufferLen +{ + cannotDie--; + if (mustDie && (cannotDie == 0)) { + [self dealloc]; + return GHTTPTrue; + } + + if(result != GHTTPSuccess) + { + NSLog(@"File Download: GHTTP Error: %d\n", result); + ghttpCleanup(); + return GHTTPTrue; + } + // create a new thumbnail using the image in the buffer, then release the buffer + NSData * jpegImage = [NSData dataWithBytes:(const void *)buffer length: bufferLen]; + if( bufferLen ) + { + [self drawThumbnailFromImage: [UIImage imageWithData: jpegImage]]; + } + + ghttpCleanup(); + [busy stopAnimating]; + busy.hidden=YES; + return GHTTPTrue; // frees the buffer +} + +- (void) downloadImageToThumbnail: (int) fileId forProfile: (int) aProfileId +{ + LeaderboardObject* leader = (LeaderboardObject*)[gLeaderboardData objectAtIndex: index]; + leader.pictureFileId = fileId; + NSLog(@"setting fileid[%d] for player[%d]=%d\n", fileId, index, leader.profileId); + + // if image is blocked, do not load it + if( [ appDelegate imageIsBlocked: fileId] ) + { + //leader.thumbNail = nil; + [busy stopAnimating]; + busy.hidden=YES; + return; + } + + GHTTPRequest request; + gsi_char url[SAKE_MAX_URL_LENGTH]; + + ghttpStartup(); + if (sakeGetFileDownloadURL(appDelegate.sake, fileId, url)) + { + NSLog(@"Download image for player[%d] URL: %s\n", leader.profileId, url); + + // download the file + request = ghttpGetEx(url, NULL, NULL, 0, NULL, GHTTPFalse, GHTTPFalse, NULL, DownloadBuddyImageCompletedCallback, self); + if (request == -1) + { + NSLog(@"Error starting image file download\n"); + } + else + { + cannotDie++; + [appDelegate startThinkTimer]; + return; + } + } + else + { + NSLog(@"Failed to get download url!\n"); + } + + ghttpCleanup(); + [busy stopAnimating]; + busy.hidden=YES; +} + +- (void) sakeSearchForRecordsRequestCallback:(SAKE) sake request:(SAKERequest) request result:(SAKERequestResult) result inputData:(void *) inputData outputData:(void *) outputData +{ + cannotDie--; + if (mustDie && (cannotDie == 0)) + { + [self dealloc]; + return; + } + + SAKESearchForRecordsInput* inData = (SAKESearchForRecordsInput*)inputData; + if(result == SAKERequestResult_SUCCESS) + { + if ( ((SAKESearchForRecordsOutput*)outputData)->mNumRecords > 0) + { + [self downloadImageToThumbnail: ((SAKEGetMyRecordsOutput*)outputData)->mRecords[0]->mValue.mInt + forProfile: (inData)->mOwnerIds[0] ]; + return; + } + else + { + //XNSLog(@"No existing profile for player[%d]\n", *((SAKESearchForRecordsInput*)(request)->mInput)->mOwnerIds[0]); + NSLog(@"No existing profile for player[%d]\n", (inData)->mOwnerIds[0]); + } + } + else + { + NSLog(@"Failed to search for player[%d] record, error: %d\n", *((inData)->mOwnerIds) ,result); + } + [busy stopAnimating]; + busy.hidden=YES; +} + +- (void)getImageFromNetwork +{ + SAKERequest request; + SAKEStartRequestResult startRequestResult; + static SAKESearchForRecordsInput sfr_input; + static char * fieldNames[] = { (char *) "picture" }; + int ownerids[] = { profileId }; + + sfr_input.mTableId = (char *) "Profiles"; + sfr_input.mFieldNames = fieldNames; + sfr_input.mNumFields = sizeof(fieldNames) / sizeof(fieldNames[0]); + sfr_input.mSort = (char *) "recordid asc"; + sfr_input.mOffset = 0; + sfr_input.mMaxRecords = 1; + sfr_input.mOwnerIds = ownerids; + sfr_input.mNumOwnerIds = 1; + + NSLog(@"get fileid for [%d]\n", profileId); + request = sakeSearchForRecords(appDelegate.sake, &sfr_input, SakeSearchForBuddyRecordsCallback, self ); + if(!request) + { + startRequestResult = sakeGetStartRequestResult(appDelegate.sake); + NSLog(@"Failed to start search for records request: %d\n", startRequestResult); + [busy stopAnimating]; + + } + else + { + NSLog(@"Searching for picture fileid record for player [%d]\n", profileId); + cannotDie++; + } +} + + +@end + +void SakeSearchForBuddyRecordsCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ) +{ + [(BuddyListCellView *)userData sakeSearchForRecordsRequestCallback: sake request: request result: result inputData: inputData outputData: outputData ]; +} + +GHTTPBool DownloadBuddyImageCompletedCallback(GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param) +{ + return [(BuddyListCellView *)param downloadCompletedCallbackRequest: request result: result buffer: buffer bufferLen: bufferLen]; +} + + diff --git a/Samples/TapRace/iphone/CreateUserController.h b/Samples/TapRace/iphone/CreateUserController.h new file mode 100644 index 00000000..ba45eee0 --- /dev/null +++ b/Samples/TapRace/iphone/CreateUserController.h @@ -0,0 +1,34 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import +#import "gp/gp.h" + +@class LoginController; + +@interface CreateUserController : UIViewController +{ + IBOutlet UITextField* emailField; + IBOutlet UITextField* userNameField; + IBOutlet UITextField* passwordField; + IBOutlet UISwitch* autoLoginSwitch; + + LoginController* loginController; + + NSTimer* loginTimer; + + GPConnection connection; + GPProfile profile; +} + +@property(nonatomic, assign) LoginController* loginController; +@property(nonatomic, readonly) GPConnection connection; +@property(nonatomic, readonly) GPProfile profile; +@property(nonatomic, readonly) NSString* uniquenick; +@property(nonatomic, readonly) NSString* password; +@property(nonatomic, retain) UISwitch * autoLoginSwitch; + +- (IBAction)createButtonClicked: (id)sender; +- (IBAction)returnClicked: (id)sender; + +@end diff --git a/Samples/TapRace/iphone/CreateUserController.mm b/Samples/TapRace/iphone/CreateUserController.mm new file mode 100644 index 00000000..e6a3eb4b --- /dev/null +++ b/Samples/TapRace/iphone/CreateUserController.mm @@ -0,0 +1,248 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "CreateUserController.h" +#import "LoginController.h" +#import "AppDelegate.h" +#import "Utility.h" + + +static void ConnectNewUserCallback(GPConnection* connection, GPConnectResponseArg* connectResponse, CreateUserController* controller); +static void ConnectCallback(GPConnection* connection, GPConnectResponseArg* connectResponse, CreateUserController* controller); +static void RegisterUniqueNickCallback(GPConnection* connection, GPRegisterUniqueNickResponseArg* registerResponse, CreateUserController* controller); + + +@interface CreateUserController(Private) + +- (void)doneClicked: (id)sender; +- (void)onLoginTimer: (NSTimer*)timer; +- (void)connectNewUserCallback: (GPResult)result profile: (GPProfile)profile uniquenick: (const char[])uniquenick; +- (void)connectCallback: (GPResult)result profile: (GPProfile)profile uniquenick: (const char[])uniquenick; +- (void)registerUniqueNickCallback: (GPResult)result; + +@end + + +@implementation CreateUserController + +@synthesize loginController; +@synthesize connection; +@synthesize profile; +@synthesize autoLoginSwitch; + +- (void)dealloc +{ + [autoLoginSwitch release]; + [emailField release]; + [userNameField release]; + [passwordField release]; + [super dealloc]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation +{ + // Return YES for supported orientations + return interfaceOrientation == UIInterfaceOrientationPortrait; +} + +- (NSString*)uniquenick +{ + return userNameField.text; +} + +- (NSString*)password +{ + return passwordField.text; +} + +- (IBAction)createButtonClicked: (id)sender +{ + // Check for no account info + //////////////////////////// + if(([userNameField.text length] == 0) || ([passwordField.text length] == 0)) + { + MessageBox(@"Please fill in all the account information."); + return; + } + + if (gpInitialize(&connection, SCRACE_PRODUCTID, WSLogin_NAMESPACE_SHARED_UNIQUE, GP_PARTNERID_GAMESPY) != GP_NO_ERROR) + { + MessageBox(@"Error initializing the login system."); + return; + } + + const char* nick = [userNameField.text cStringUsingEncoding: NSASCIIStringEncoding]; + const char* email = [emailField.text cStringUsingEncoding: NSASCIIStringEncoding]; + const char* password = [passwordField.text cStringUsingEncoding: NSASCIIStringEncoding]; + + GPResult result = gpConnectNewUserA(&connection, nick, nick, email, password, NULL, GP_FIREWALL, GP_NON_BLOCKING, (GPCallback)ConnectNewUserCallback, self); + + if (result != GP_NO_ERROR) { + char errorString[GP_ERROR_STRING_LEN]; + + switch (result) { + case GP_PARAMETER_ERROR: + gpGetErrorStringA(&connection, errorString); + MessageBox([NSString stringWithCString: errorString encoding: NSASCIIStringEncoding], @"Invalid Data"); + //MessageBox(@"One of the input fields contains invalid data."); + break; + + default: + gpGetErrorStringA(&connection, errorString); + MessageBox([NSString stringWithCString: errorString encoding: NSASCIIStringEncoding]); + } + + gpDisconnect(&connection); + gpDestroy(&connection); + return; + } + + loginTimer = [NSTimer scheduledTimerWithTimeInterval: 0.050 target: self selector: @selector(onLoginTimer:) userInfo: nil repeats: YES]; +} + +- (IBAction)returnClicked: (id)sender +{ + UITextField* textFields[] = { emailField, userNameField, passwordField }; + size_t textFieldCount = sizeof(textFields) / sizeof(textFields[0]); + size_t textFieldIndex = 0; + + // Find the text field that generated the event. + while (textFields[textFieldIndex] != sender) + textFieldIndex++; + + // Cycle focus to the next text field. + if (++textFieldIndex < textFieldCount) { + [textFields[textFieldIndex] becomeFirstResponder]; + } + else { + [(UIResponder*)sender resignFirstResponder]; + } +} + +@end + + +@implementation CreateUserController(Private) + +- (void)textFieldDidBeginEditing: (UITextField*)textField +{ + self.navigationItem.rightBarButtonItem = [[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemDone target: self action: @selector(doneClicked:)]; +} + +- (void)textFieldDidEndEditing:(UITextField *)textField +{ + self.navigationItem.rightBarButtonItem = nil; +} + +- (void)doneClicked: (id)sender +{ + UITextField* textFields[] = { emailField, userNameField, passwordField }; + size_t textFieldCount = sizeof(textFields) / sizeof(textFields[0]); + + for (size_t n = 0; n < textFieldCount; n++) { + [textFields[n] resignFirstResponder]; + } +} + +- (void)onLoginTimer: (NSTimer*)timer +{ + gpProcess(&connection); +} + +- (void)connectNewUserCallback: (GPResult)result profile: (GPProfile)userProfile uniquenick: (const char[])uniquenick +{ + [loginTimer invalidate]; + loginTimer = nil; + + if (result != GP_NO_ERROR) { + GPErrorCode errorCode; + char errorString[GP_ERROR_STRING_LEN]; + gpGetErrorCode(&connection, &errorCode); + + switch (errorCode) { + case GP_NEWUSER_BAD_PASSWORD: + MessageBox(@"There is an existing account with the given e-mail address, but the password does not match. To use your existing " + @"GameSpy account, provide the correct password for the account.", @"Invalid Password"); + break; + + case GP_NEWUSER_BAD_NICK: + { + // This may mean that the user already has a profile with this nick; try to connect to it, and then try + // to register a corresponding uniquenick. + const char* nick = [userNameField.text cStringUsingEncoding: NSASCIIStringEncoding]; + const char* email = [emailField.text cStringUsingEncoding: NSASCIIStringEncoding]; + const char* password = [passwordField.text cStringUsingEncoding: NSASCIIStringEncoding]; + + gpDestroy(&connection); + gpInitialize(&connection, SCRACE_PRODUCTID, WSLogin_NAMESPACE_SHARED_UNIQUE, GP_PARTNERID_GAMESPY); + gpConnectA(&connection, nick, email, password, GP_FIREWALL, GP_NON_BLOCKING, (GPCallback)ConnectCallback, self); + loginTimer = [NSTimer scheduledTimerWithTimeInterval: 0.050 target: self selector: @selector(onLoginTimer:) userInfo: nil repeats: YES]; + return; + } + + default: + gpGetErrorStringA(&connection, errorString); + MessageBox([NSString stringWithCString: errorString encoding: NSASCIIStringEncoding], @"Try Again" ); + } + + gpDisconnect(&connection); + gpDestroy(&connection); + return; + } + + profile = userProfile; + [loginController userProfileCreated: self autoLogin: autoLoginSwitch.on]; +} + +- (void)connectCallback: (GPResult)result profile: (GPProfile)userProfile uniquenick: (const char[])uniquenick +{ + if (result != GP_NO_ERROR) { + char errorString[GP_ERROR_STRING_LEN]; + gpGetErrorStringA(&connection, errorString); + MessageBox([NSString stringWithCString: errorString encoding: NSASCIIStringEncoding]); + gpDisconnect(&connection); + gpDestroy(&connection); + [loginTimer invalidate]; + loginTimer = nil; + return; + } + + profile = userProfile; + gpRegisterUniqueNickA(&connection, [userNameField.text cStringUsingEncoding: NSASCIIStringEncoding], NULL, GP_NON_BLOCKING, (GPCallback)RegisterUniqueNickCallback, self); +} + +- (void)registerUniqueNickCallback: (GPResult)result +{ + [loginTimer invalidate]; + loginTimer = nil; + + if (result != GP_NO_ERROR) { + char errorString[GP_ERROR_STRING_LEN]; + gpGetErrorStringA(&connection, errorString); + MessageBox([NSString stringWithCString: errorString encoding: NSASCIIStringEncoding]); + gpDisconnect(&connection); + gpDestroy(&connection); + return; + } + + [loginController userProfileCreated: self autoLogin: autoLoginSwitch.on]; +} + +@end + + +void ConnectNewUserCallback(GPConnection* connection, GPConnectResponseArg* connectResponse, CreateUserController* controller) +{ + [controller connectNewUserCallback: connectResponse->result profile: connectResponse->profile uniquenick: connectResponse->uniquenick]; +} + +void ConnectCallback(GPConnection* connection, GPConnectResponseArg* connectResponse, CreateUserController* controller) +{ + [controller connectCallback: connectResponse->result profile: connectResponse->profile uniquenick: connectResponse->uniquenick]; +} + +void RegisterUniqueNickCallback(GPConnection* connection, GPRegisterUniqueNickResponseArg* registerResponse, CreateUserController* controller) +{ + [controller registerUniqueNickCallback: registerResponse->result]; +} + diff --git a/Samples/TapRace/iphone/FlipsideView.h b/Samples/TapRace/iphone/FlipsideView.h new file mode 100644 index 00000000..77da06e7 --- /dev/null +++ b/Samples/TapRace/iphone/FlipsideView.h @@ -0,0 +1,13 @@ +// +// FlipsideView.h +// flipview +// +// Created by Paul Berlinguette on 6/11/09. +// Copyright __MyCompanyName__ 2009. All rights reserved. +// + +@interface FlipsideView : UIView { + +} + +@end diff --git a/Samples/TapRace/iphone/FlipsideView.m b/Samples/TapRace/iphone/FlipsideView.m new file mode 100644 index 00000000..27356c40 --- /dev/null +++ b/Samples/TapRace/iphone/FlipsideView.m @@ -0,0 +1,32 @@ +// +// FlipsideView.m +// flipview +// +// Created by Paul Berlinguette on 6/11/09. +// Copyright __MyCompanyName__ 2009. All rights reserved. +// + +#import "FlipsideView.h" + +@implementation FlipsideView + + +- (id)initWithFrame:(CGRect)frame { + if ( (self = [super initWithFrame:frame]) ) { + // Initialization code + } + return self; +} + + +- (void)drawRect:(CGRect)rect { + // Drawing code +} + + +- (void)dealloc { + [super dealloc]; +} + + +@end diff --git a/Samples/TapRace/iphone/FlipsideViewController.h b/Samples/TapRace/iphone/FlipsideViewController.h new file mode 100644 index 00000000..05f99bfa --- /dev/null +++ b/Samples/TapRace/iphone/FlipsideViewController.h @@ -0,0 +1,27 @@ +// +// FlipsideViewController.h +// flipview +// +// Created by Paul Berlinguette on 6/11/09. +// Copyright __MyCompanyName__ 2009. All rights reserved. +// +#import + +@protocol FlipsideViewControllerDelegate; + +@interface FlipsideViewController : UIViewController { + id delegate; + IBOutlet UIButton* clearBlockedImagesButton; +} + +@property (nonatomic, assign) id delegate; +- (IBAction)done; +- (IBAction)contactUs:(id)sender; +- (IBAction)clearBlockedImagesFile; + +@end +@protocol FlipsideViewControllerDelegate + +- (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller; +@end + diff --git a/Samples/TapRace/iphone/FlipsideViewController.mm b/Samples/TapRace/iphone/FlipsideViewController.mm new file mode 100644 index 00000000..0da18687 --- /dev/null +++ b/Samples/TapRace/iphone/FlipsideViewController.mm @@ -0,0 +1,109 @@ +// +// FlipsideViewController.m +// flipview +// +// Created by Paul Berlinguette on 6/11/09. +// Copyright __MyCompanyName__ 2009. All rights reserved. +// +#import "Utility.h" +#import "AppDelegate.h" +#import "FlipsideViewController.h" + + + +@implementation FlipsideViewController + +@synthesize delegate; + + +- (void)viewDidLoad { + [super viewDidLoad]; + self.view.backgroundColor = [UIColor viewFlipsideBackgroundColor]; +} + + +- (IBAction)done +{ + [ appDelegate flipsideViewControllerDidFinish: self.delegate]; +} + +#pragma mark User Interface Actions + +- (IBAction)contactUs:(id)sender { + + // Warn if cannot send mail + if(! [MFMailComposeViewController canSendMail] ) + { + MessageBox( @"Device is unable to send an email at the moment."); + return; + } + + MFMailComposeViewController *composeVC = [[MFMailComposeViewController alloc] init]; + + composeVC.mailComposeDelegate = self; + + [composeVC setSubject:@"GameSpy TapRace - Request info on iPhone tech"]; + + NSString *messageBody = [NSString stringWithFormat:@"Hi,\n\n" + "I am a developer and I have played TapRace on the iPhone and would like more information on GameSpy's social gaming and multi-player services\n\n" + "Please contact me at xxx-xxx-xxxx or reply to my email.\n\n" + "Thanks you,\n\n" + "\n\n" + ];//"Thanks, - %@ by %@\n", title, artist]; + [composeVC setMessageBody:messageBody isHTML:NO]; + + [composeVC setToRecipients: [NSArray arrayWithObject:@"devrelations@gamespy.com"]]; + + [self presentModalViewController:composeVC animated:YES]; + [composeVC release]; +} + +- (IBAction)clearBlockedImagesFile +{ + NSString* dataPath; + + //*** Remove player's block image file + dataPath = [gPlayerData.playerDataPath stringByAppendingPathComponent: blockedImagesDataFile]; + + BOOL isDir = NO; + NSFileManager* fileManager = [NSFileManager defaultManager]; + if ([fileManager fileExistsAtPath: dataPath isDirectory: &isDir]) + { + [fileManager removeItemAtPath: dataPath error: NULL]; + + } + + [appDelegate deleteBlockedImagesData ]; +} + + +- (void)mailComposeController:(MFMailComposeViewController *)controller didFinishWithResult:(MFMailComposeResult)result error:(NSError *)error { + [self dismissModalViewControllerAnimated:YES]; +} + +/* + // Override to allow orientations other than the default portrait orientation. + - (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + return (interfaceOrientation == UIInterfaceOrientationPortrait); + } + */ + +- (void)didReceiveMemoryWarning { + // Releases the view if it doesn't have a superview. + [super didReceiveMemoryWarning]; + + // Release any cached data, images, etc that aren't in use. +} + +- (void)viewDidUnload { + // Release any retained subviews of the main view. + // e.g. self.myOutlet = nil; +} + + +- (void)dealloc { + [super dealloc]; +} + +@end diff --git a/Samples/TapRace/iphone/ForgotPassword.h b/Samples/TapRace/iphone/ForgotPassword.h new file mode 100644 index 00000000..c6228e44 --- /dev/null +++ b/Samples/TapRace/iphone/ForgotPassword.h @@ -0,0 +1,16 @@ +// +// ForgotPassword.h +// Gamespy +// +// Created by Puap Puap on 10/27/08. +// Copyright 2008 Pick Up And Play. All rights reserved. +// + +#import + + +@interface ForgotPassword : UIViewController { + IBOutlet UIWebView * webView; +} + +@end diff --git a/Samples/TapRace/iphone/ForgotPassword.mm b/Samples/TapRace/iphone/ForgotPassword.mm new file mode 100644 index 00000000..32aabc22 --- /dev/null +++ b/Samples/TapRace/iphone/ForgotPassword.mm @@ -0,0 +1,59 @@ +// +// ForgotPassword.mm +// Gamespy +// +// Created by Puap Puap on 10/27/08. +// Copyright 2008 Pick Up And Play. All rights reserved. +// + +#import "ForgotPassword.h" + + +@implementation ForgotPassword + +// Override initWithNibName:bundle: to load the view using a nib file then perform additional customization that is not appropriate for viewDidLoad. +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + if ( (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) ) + { + NSString* stringurl = @"https://login.gamespyid.com/lostpassword.aspx"; + NSURL * url = [NSURL URLWithString: stringurl]; +// NSURLRequest * urlrequest = [NSURLRequest requestWithURL: url]; +// [webView loadRequest: urlrequest]; + + [[UIApplication sharedApplication] openURL:url]; + } + return self; +} + +/* +// Implement loadView to create a view hierarchy programmatically. +- (void)loadView { +} +*/ + +/* +// Implement viewDidLoad to do additional setup after loading the view. +- (void)viewDidLoad { + [super viewDidLoad]; +} +*/ + + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation { + // Return YES for supported orientations + return (interfaceOrientation == UIInterfaceOrientationPortrait); +} + + +- (void)didReceiveMemoryWarning { + [super didReceiveMemoryWarning]; // Releases the view if it doesn't have a superview + // Release anything that's not essential, such as cached data +} + + +- (void)dealloc { + [super dealloc]; +} + + +@end diff --git a/Samples/TapRace/iphone/GameController.h b/Samples/TapRace/iphone/GameController.h new file mode 100644 index 00000000..ca258bea --- /dev/null +++ b/Samples/TapRace/iphone/GameController.h @@ -0,0 +1,49 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import + + +@interface GameController : UIViewController +{ + IBOutlet UIButton* tapButton1; + IBOutlet UIButton* tapButton2; + IBOutlet UIProgressView* localProgress; + IBOutlet UIProgressView* remoteProgress; + IBOutlet UILabel* localPlayerLabel; + IBOutlet UILabel* remotePlayerLabel; + IBOutlet UILabel* gameTimeLabel; + IBOutlet UILabel* previousBestLabel; + IBOutlet UILabel* previousBestTimeLabel; + IBOutlet UILabel* messageLabel; + IBOutlet UIActivityIndicatorView* activityIndicator; + + NSTimer* updateTimer; + NSArray* gameControls; + + NSString* initialMessageText; + + int numSteps, prevNumSteps; + + bool opponentFinished; +} + +@property(nonatomic, readonly) int numSteps; +@property(nonatomic, readwrite ) int prevNumSteps; + +- (IBAction)startButtonClicked: (id)sender; +- (IBAction)tapButtonPressed: (id)sender; +- (IBAction)showInfo; +- (IBAction)gotoMenu; + +//- (void)gotoMenu: (id)sender; +- (void)countdown: (int)count; +- (void)stopRaceClock; +- (void)raceStarted; +- (void)doneRacing; +- (void)remoteProgress: (int)progress; +- (void)setRemoteNick: (NSString*)nick; + +- (void)reset; + +@end diff --git a/Samples/TapRace/iphone/GameController.mm b/Samples/TapRace/iphone/GameController.mm new file mode 100644 index 00000000..33825a14 --- /dev/null +++ b/Samples/TapRace/iphone/GameController.mm @@ -0,0 +1,297 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "GameController.h" +#import "AppDelegate.h" +#import "Utility.h" + +#import + +#if TARGET_IPHONE_SIMULATOR +static const int TOTAL_STEPS = 3; +#else +static const int TOTAL_STEPS = 60; +#endif + + + +@interface GameController(Private) + +- (void)updateTime: (NSTimer*)timer; + +@end + + +@implementation GameController + +@synthesize numSteps; +@synthesize prevNumSteps; + +- (id)initWithCoder: (NSCoder*)decoder +{ + self = [super initWithCoder: decoder]; + + if (self != nil) + { + numSteps = prevNumSteps = 0; + opponentFinished = false; + } + + return self; +} + +- (void)dealloc +{ + [tapButton1 release]; + [tapButton2 release]; + [localProgress release]; + [remoteProgress release]; + [localPlayerLabel release]; + [remotePlayerLabel release]; + [gameTimeLabel release]; + [previousBestLabel release]; + [previousBestTimeLabel release]; + [messageLabel release]; + [activityIndicator release]; + [gameControls release]; + [initialMessageText release]; + [super dealloc]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + // Return YES for supported orientations + return interfaceOrientation == UIInterfaceOrientationPortrait; +} + +- (void)viewDidLoad +{ + gameControls = [[NSArray alloc] initWithObjects: tapButton1, tapButton2, nil]; + + if (messageLabel != nil) + { + initialMessageText = [messageLabel.text copy]; + } + + // if Two-player game, set button back to matchmaking + if ( [appDelegate isTwoPlayerGame] ) + { + // set right button to go to menu page + UIBarButtonItem* aBarButton = [ [UIBarButtonItem alloc] initWithTitle:@"Menu" + style:UIBarButtonItemStyleBordered + target:self + action:@selector(gotoMenu)]; + self.navigationItem.rightBarButtonItem = aBarButton; + [aBarButton release]; + + aBarButton = [ [UIBarButtonItem alloc] initWithTitle:@"MatchMaking" + style:UIBarButtonItemStyleBordered + target:self + action:@selector(gotoMatchMaking)]; + self.navigationItem.leftBarButtonItem = aBarButton; + [aBarButton release]; + } +} + +- (void)viewWillAppear: (BOOL)animated +{ + // if Single-player game + if (! [appDelegate isTwoPlayerGame] ) + { + self.title = @"Single Player Game"; + + // show previous best time + if (gPlayerData.playerStatsData != nil) + { + int previousBestTimeMillis = [(NSNumber*)[gPlayerData.playerStatsData objectForKey: singlePlayerBestTimeKey] intValue]; + + previousBestTimeLabel.text = [NSString stringWithFormat: timeFormatString, previousBestTimeMillis / (60 * 1000), (previousBestTimeMillis % (60 * 1000)) / 1000.0f]; + previousBestLabel.hidden = NO; + previousBestTimeLabel.hidden = NO; + + messageLabel.text= @"Start tapping the keys to begin!"; + messageLabel.hidden = NO; + } + else + { + previousBestLabel.hidden = YES; + previousBestTimeLabel.hidden = YES; + } + } +} + +- (void)viewWillDisappear: (BOOL)animated +{ + if (updateTimer != nil) + { + [updateTimer invalidate]; + updateTimer = nil; + } +} + +- (void)viewDidAppear: (BOOL)animated +{ + // Two-player game + if ( [appDelegate isTwoPlayerGame] ) + { + // start immediately + [appDelegate startCountdown]; + } + else if( appDelegate.fromMenu ) // Single player game + { + MessageBoxNoBlock(@"Alternate tapping the keys. The faster you tap, the better your time will be! ", @"How To Play", @"Continue" ); + } + +} + +// Display the About page +- (IBAction)showInfo +{ + [ appDelegate showAboutInfo: self ]; +} + +- (IBAction)startButtonClicked: (id)sender +{ + // Single-player game; tap button + [tapButton1 removeTarget: self action: @selector(startButtonClicked:) forControlEvents: UIControlEventAllEvents]; + [tapButton1 addTarget: self action: @selector(tapButtonPressed:) forControlEvents: UIControlEventTouchDown]; + + tapButton1.enabled = NO; + tapButton2.enabled = YES; + + messageLabel.hidden = YES; + + numSteps = 1; + localProgress.progress = numSteps / (float)TOTAL_STEPS; + + [appDelegate startCountdown]; +} + +- (IBAction)tapButtonPressed: (id)sender +{ + tapButton1.enabled = !tapButton1.enabled; + tapButton2.enabled = !tapButton2.enabled; + + ++numSteps; + localProgress.progress = numSteps / (float)TOTAL_STEPS; + + if(numSteps == TOTAL_STEPS) + { + if(!opponentFinished) + messageLabel.text = @"Waiting for opponent to finish race"; + + [appDelegate finishedGame]; + + [updateTimer invalidate]; + updateTimer = nil; + + tapButton1.enabled = NO; + tapButton2.enabled = NO; + } +} + +- (IBAction)gotoMenu +{ + [appDelegate returnToMenu]; +} + +- (IBAction)gotoMatchMaking +{ + [appDelegate returnToMatchMaking]; +} + +// stop race clock when disconnected or race canceled +- (void)stopRaceClock +{ + [updateTimer invalidate]; + updateTimer = nil; +} + + +// 5 second start of race countdown +- (void)countdown: (int)count +{ + if(count) + messageLabel.text = [NSString stringWithFormat: @"Race starts in %ds", count]; +} + +- (void)raceStarted +{ + updateTimer = [NSTimer scheduledTimerWithTimeInterval: TAP_POLL_TIME target: self selector: @selector(updateTime:) userInfo: nil repeats: YES]; + if (remoteProgress == nil) { + // If in single-player mode, the game is already active. + return; + } + + /*for (UIControl* control in gameControls) { + control.enabled = YES; + }*/ + tapButton1.enabled = YES; + tapButton2.enabled = NO; + + messageLabel.text = @"Go!"; + [activityIndicator stopAnimating]; +} + +- (void)doneRacing +{ +// uint64_t localTime = mach_absolute_time() - appDelegate.start; +// gameTimeLabel.text = [NSString stringWithFormat: timeFormatString, localTime / (60 * 1000), (localTime % (60 * 1000)) / 1000.0f]; +// gameTimeLabel.text = [NSString stringWithFormat: @"%0.2f", localTime * absolute_to_seconds ]; + + messageLabel.text = @"Race Complete"; + + localProgress.progress = 0.0; + remoteProgress.progress = 0.0; +} + +- (void)remoteProgress: (int)progress +{ + remoteProgress.progress = progress / (float)TOTAL_STEPS; +} + +- (void)setRemoteNick: (NSString*)nick +{ + remotePlayerLabel.text = nick; +} + +- (void)reset +{ + gameTimeLabel.text = [NSString stringWithFormat: timeFormatString, 0, 0.0]; + + if (remoteProgress == nil) + { + tapButton1.enabled = YES; + tapButton2.enabled = NO; + + // First touch starts the game. + [tapButton1 removeTarget: self action: NULL forControlEvents: UIControlEventAllEvents]; + [tapButton1 addTarget: self action: @selector(startButtonClicked:) forControlEvents: UIControlEventTouchDown]; + + numSteps = 0; + localProgress.progress = 0.0; + } + else + { + numSteps = 0; + + localProgress.progress = 0.0; + remoteProgress.progress = 0.0; + + [activityIndicator startAnimating]; + messageLabel.text = initialMessageText; + } +} + +@end + + +@implementation GameController(Private) + +- (void)updateTime: (NSTimer*)timer; +{ + unsigned int timeElapsed = (mach_absolute_time() - appDelegate.start) * absolute_to_millis; + gameTimeLabel.text = [NSString stringWithFormat: timeFormatString, timeElapsed / (60 * 1000), (timeElapsed % (60 * 1000)) / 1000.0f]; +} + +@end diff --git a/Samples/TapRace/iphone/GameResultsController.h b/Samples/TapRace/iphone/GameResultsController.h new file mode 100644 index 00000000..2dd1d728 --- /dev/null +++ b/Samples/TapRace/iphone/GameResultsController.h @@ -0,0 +1,72 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import + + +@interface GameResultsController : UIViewController +{ + // Single-player controls: + IBOutlet UILabel* gameTimeLabel; + IBOutlet UILabel* gamesPlayedLabel; + IBOutlet UILabel* bestTimeLabel; + IBOutlet UILabel* averageTimeLabel; + IBOutlet UILabel* congratsLabel; + IBOutlet UILabel* newbestLabel; + IBOutlet UILabel* newbestTimeLabel; + IBOutlet UILabel* oldbestLabel; + IBOutlet UILabel* avgTapsPerSecLabel; + + // Multi-player controls: + IBOutlet UILabel* tieLabel; + + IBOutlet UILabel* localWinnerLabel; + IBOutlet UILabel* localPlayerLabel; + IBOutlet UILabel* localTimeLabel; + IBOutlet UIButton* localThumbnailButton; // so that we can grey out loser + //IBOutlet UIImageView* localThumbnail; + + IBOutlet UILabel* remoteWinnerLabel; + IBOutlet UILabel* remotePlayerLabel; + IBOutlet UILabel* remoteTimeLabel; + IBOutlet UIButton* remoteThumbnailButton; + //IBOutlet UIImageView* remoteThumbnail; + + // Common controls: + IBOutlet UIButton* leaderboardsButton; + IBOutlet UIButton* playAgainButton; + IBOutlet UIButton* menuButton; + + IBOutlet UIActivityIndicatorView* busyLocal; + IBOutlet UIActivityIndicatorView* busyRemote; + + int localTime; + int remoteTime; +} + +- (IBAction)leaderboardsButtonClicked: (id)sender; +- (IBAction)playAgainButtonClicked: (id)sender; +- (IBAction)menuButtonClicked: (id)sender; +- (IBAction)viewOpponentClicked: (id)sender; +- (IBAction)viewMyStatsClicked: (id)sender; + +- (IBAction)showInfo; +- (IBAction)gotoMenu; +- (IBAction)gotoMatchMaking; + +- (void)setGamesPlayed: (int)gamesPlayed; +- (void)setBestTime: (int)bestTime; +- (void)setAverageTime: (int)averageTime; +- (void)setAverageTaps: (float) avgTaps; + +- (void)setLocalPlayer: (NSString*)playerName time: (int)time; +- (void)setRemotePlayer: (NSString*)playerName time: (int)time; + +- (void)setGameTime: (int)gameTime; +- (void)setWinner; +- (void)setNewBestTime: (int)oldTime; +- (void) getImagesForResultsPage; +- (void) drawThumbnailImage: (UIImage*)image forPlayer: (int) profileId; + +- (void)connectionDropped; +@end diff --git a/Samples/TapRace/iphone/GameResultsController.mm b/Samples/TapRace/iphone/GameResultsController.mm new file mode 100644 index 00000000..00068c14 --- /dev/null +++ b/Samples/TapRace/iphone/GameResultsController.mm @@ -0,0 +1,639 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "GameResultsController.h" +#import "AppDelegate.h" +#import "Utility.h" + +#import + +typedef struct serverParam_struct +{ + GameResultsController * controller; + int profileId; +} ServerParam; + +// callback prototypes +// http stuff to upload and download image +//void postCallback( GHTTPRequest request, int bytesPosted, int totalBytes, int objectsPosted, int totalObjects, void * param ); +//GHTTPBool UploadCompletedCallback(GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param); +GHTTPBool DownloadImageCompletedCallback(GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param); + +// sake stuff to access and create persistent server storage +//void SakeCreateRecordCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ); +//void SakeUpdateRecordCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ); +//void SakeTestRecordCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ); +void SakeSearchForImageFileIdRecordsCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ); + +//static SAKEField fields[2]; + +static bool discardCallbacks = true; + + + + +@interface GameResultsController(Private) + +- (void)determineWinner; + +@end + + +@implementation GameResultsController + +- (void)dealloc +{ + discardCallbacks = true; + [oldbestLabel release]; + [avgTapsPerSecLabel release]; + [congratsLabel release]; + [newbestLabel release]; + [newbestTimeLabel release]; + [gamesPlayedLabel release]; + [bestTimeLabel release]; + [averageTimeLabel release]; + [localWinnerLabel release]; + [remoteWinnerLabel release]; + [tieLabel release]; + [localPlayerLabel release]; + [localTimeLabel release]; + [localThumbnailButton release]; + [remotePlayerLabel release]; + [remoteTimeLabel release]; + [remoteThumbnailButton release]; + [gameTimeLabel release]; + [leaderboardsButton release]; + [playAgainButton release]; + [menuButton release]; + [super dealloc]; +} + + +// Common controls: +IBOutlet UIButton* leaderboardsButton; +IBOutlet UIButton* playAgainButton; +IBOutlet UIButton* menuButton; + +- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation +{ + // Return YES for supported orientations + return interfaceOrientation == UIInterfaceOrientationPortrait; +} + +- (void)viewWillAppear: (BOOL)animated +{ + [super viewWillAppear: animated]; + self.title = @"Race Result"; + //self.backBarButtonItem.title = @"Menu"; + + //[localThumbnail setImage: gPlayerData.thumbNail ]; + //[remoteThumbnail setImage: gPlayerData.thumbNail ]; + self.navigationItem.hidesBackButton = YES; + discardCallbacks = false; +} + + +- (void)viewDidLoad +{ + // set right button to go to menu page + UIBarButtonItem* aBarButton = [ [UIBarButtonItem alloc] initWithTitle:@"Menu" + style:UIBarButtonItemStyleBordered + target:self + action:@selector(gotoMenu)]; + + // if Two-player game, set left button back to matchmaking + if( [ appDelegate isTwoPlayerGame ] ) + { + self.navigationItem.rightBarButtonItem = aBarButton; + [aBarButton release]; + + aBarButton = [ [UIBarButtonItem alloc] initWithTitle:@"MatchMaking" + style:UIBarButtonItemStyleBordered + target:self + action:@selector(gotoMatchMaking)]; + self.navigationItem.leftBarButtonItem = aBarButton; + [aBarButton release]; + } + else + { + self.navigationItem.leftBarButtonItem = aBarButton; + [aBarButton release]; + } +} + + +- (IBAction)viewOpponentClicked: (id)sender +{ + //[appDelegate showLeaderboards]; +} + +- (IBAction)viewMyStatsClicked: (id)sender +{ + [appDelegate showLeaderboards]; +} + + +- (IBAction)leaderboardsButtonClicked: (id)sender +{ + [appDelegate showLeaderboards]; +} + +- (IBAction)playAgainButtonClicked: (id)sender +{ + [appDelegate restartGame]; +} + +- (IBAction)menuButtonClicked: (id)sender +{ + [appDelegate returnToMenu]; +} + + +- (IBAction)gotoMatchMaking +{ + [appDelegate returnToMatchMaking]; +} + +- (IBAction)gotoMenu +{ + [appDelegate returnToMenu]; +} + +// Display the About page +- (IBAction)showInfo +{ + [ appDelegate showAboutInfo: self ]; +} + +- (void)setGamesPlayed: (int)gamesPlayed +{ + gamesPlayedLabel.text = [NSString stringWithFormat: @"%d", gamesPlayed]; +} + + +- (void)setBestTime: (int)bestTime +{ + SetTimeLabel(bestTimeLabel, bestTime); +} + +- (void)setNewBestTime: (int)oldTime +{ + SetTimeLabel(newbestTimeLabel, oldTime); + congratsLabel.hidden = NO; + newbestLabel.hidden = NO; + newbestTimeLabel.hidden = NO; + oldbestLabel.hidden = NO; +} + + +- (void) getImagesForResultsPage +{ + SAKERequest request; + SAKEStartRequestResult startRequestResult; + static SAKESearchForRecordsInput sfr_input; + static char * fieldNames[] = { (char *) "ownerid", (char *) "picture" }; + //int ownerids[3]; + static int ownerids[2] = { gPlayerData.profileId, gOpponentData.profileId }; + + if( gPlayerData.thumbNail == nil && gOpponentData.thumbNail == nil ) + { + ownerids[0] = gPlayerData.profileId; + ownerids[1] = gOpponentData.profileId; +// ownerids[2] = '\0'; + + [busyLocal startAnimating ]; + [busyRemote startAnimating ]; + busyLocal.hidden=NO; + busyRemote.hidden=NO; + + sfr_input.mNumOwnerIds = 2; + sfr_input.mMaxRecords = 2; + NSLog(@"getImagesForResultsPage for player[%d] and opponent[%d]",gPlayerData.profileId,gOpponentData.profileId); + } + else if( gPlayerData.thumbNail != nil ) + { + // display player picture + [localThumbnailButton setBackgroundImage:gPlayerData.thumbNail forState: UIControlStateNormal ]; + busyLocal.hidden=YES; + [busyLocal stopAnimating]; + + // get opponent picture + ownerids[0] = gOpponentData.profileId; + ownerids[1] = '\0'; + sfr_input.mNumOwnerIds = 1; + sfr_input.mMaxRecords = 1; + [busyRemote startAnimating ]; + busyLocal.hidden=NO; + NSLog(@"getImagesForResultsPage for opponent"); + } + else if( gOpponentData.thumbNail != nil ) + { + // display opponent picture + [remoteThumbnailButton setBackgroundImage:gOpponentData.thumbNail forState: UIControlStateNormal ]; + busyRemote.hidden=YES; + [busyRemote stopAnimating]; + + // get player picture + ownerids[0] = gPlayerData.profileId; + ownerids[1] = '\0'; + sfr_input.mNumOwnerIds = 1; + sfr_input.mMaxRecords = 1; + busyLocal.hidden=NO; + [busyLocal startAnimating]; + NSLog(@"getImagesForResultsPage for player"); + } + else + { + // get no pictures + busyLocal.hidden=YES; + busyRemote.hidden=YES; + [busyLocal stopAnimating]; + [busyRemote stopAnimating]; + + [localThumbnailButton setBackgroundImage:gPlayerData.thumbNail forState: UIControlStateNormal ]; + [remoteThumbnailButton setBackgroundImage:gOpponentData.thumbNail forState: UIControlStateNormal ]; + NSLog(@"getImagesForResultsPage for no one"); + return; + } + + sfr_input.mTableId = (char *) "Profiles"; + sfr_input.mFieldNames = fieldNames; + sfr_input.mNumFields = 2; + sfr_input.mSort = (char *) "recordid asc"; + sfr_input.mOffset = 0; + sfr_input.mOwnerIds = ownerids; + + request = sakeSearchForRecords(appDelegate.sake, &sfr_input, SakeSearchForImageFileIdRecordsCallback, self ); + if(!request) + { + startRequestResult = sakeGetStartRequestResult(appDelegate.sake); + NSLog(@"Failed to start search for records request: %d\n", startRequestResult); + } + else + { + NSLog(@"Searching for player record\n"); + } +} + +- (void) downloadThumbnail: (int) fileId forPlayer: (int) profileId +{ + GHTTPRequest request; + gsi_char url[SAKE_MAX_URL_LENGTH]; + + ghttpStartup(); + if (sakeGetFileDownloadURL(appDelegate.sake, fileId, url)) + { + NSLog(@"Download image URL: %s\n", url); + + ServerParam *param = (ServerParam*) gsimalloc(sizeof(ServerParam)); + param->controller = self; + param->profileId = profileId; + + // download the file + request = ghttpGetEx(url, NULL, NULL, 0, NULL, GHTTPFalse, GHTTPFalse, NULL, DownloadImageCompletedCallback, (void *) param); + if (request == -1) + { + gsifree(param); + NSLog(@"Error starting image file download\n"); + if( (unsigned int)fileId == gPlayerData.pictureFileId ) + [busyLocal stopAnimating]; + else + [busyRemote stopAnimating]; + } + else + { + [appDelegate startThinkTimer]; + return; + } + } + else + { + NSLog(@"Failed to get download url!\n"); + + if( (unsigned int)fileId == gPlayerData.pictureFileId ) + [busyLocal stopAnimating]; + else + [busyRemote stopAnimating]; + } + ghttpCleanup(); +} + + +- (void)setAverageTime: (int)averageTime +{ + SetTimeLabel(averageTimeLabel, averageTime); +} + +- (void)setAverageTaps: (float) avgTaps +{ + avgTapsPerSecLabel.text = [NSString stringWithFormat: @"%5.3f", avgTaps]; +} +- (void)setLocalPlayer: (NSString*)playerName time: (int)time +{ + localPlayerLabel.text = playerName; + SetTimeLabel(localTimeLabel, time); + localTime = time; +} + +- (void)setRemotePlayer: (NSString*)playerName time: (int)time +{ + remotePlayerLabel.text = playerName; + SetTimeLabel(remoteTimeLabel, time); + remoteTime = time; +} + +- (void)setGameTime: (int)gameTime +{ + SetTimeLabel(gameTimeLabel, gameTime); +} + +- (void)setWinner +{ + [self determineWinner]; +} + +- (void)connectionDropped +{ + playAgainButton.enabled = NO; +} + +// Resize the downloaded image and save in gPlayerData.fullsizeImage to represent the profile size +// Resize the fullsizeimage to and save in gPlayerData.thumbnail to represent the leaderboard thumbnail size +// Then draw thumbnail +- (void) drawThumbnailImage: (UIImage*)image forPlayer: (int) profileId +{ + + // get the current colorspace + int imgh = image.size.height; + int imgw = image.size.width; + CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB( ); + void * data = calloc(imgh, 4*imgw); + CGContextRef bmpContextRef = CGBitmapContextCreate ( data, // void *data, + imgw, // size_t width, + imgh, // size_t height, + 8, // size_t bitsPerComponent, + 4*imgw, // size_t bytesPerRow, + colorspace, // CGColorSpaceRef colorspace, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big // CGBitmapInfo bitmapInfo + ); + CGRect fsrect = CGRectMake( 0, 0, imgw, imgh ); + CGContextDrawImage ( bmpContextRef, fsrect, image.CGImage ); // draw (and resize) image + //if (gPlayerData.fullsizeImage != nil) [gPlayerData.fullsizeImage release]; + //gPlayerData.fullsizeImage = [[UIImage imageWithCGImage: CGBitmapContextCreateImage ( bmpContextRef )] retain]; + CGContextRelease(bmpContextRef); + free(data); + + // now redraw into thumbnail + int imageOrientation = image.imageOrientation; + int th,tw; + float hscale = ((float)imgh)/float(THUMBNAIL_HEIGHT); + float wscale = ((float)imgw)/float(THUMBNAIL_WIDTH); + if (hscale > wscale) { + // scale to height + th = THUMBNAIL_HEIGHT; + tw = (int)((float)imgw/hscale); + } else { + tw = THUMBNAIL_WIDTH; + th = (int)((float)imgh/wscale); + } + data = calloc(th, 4*tw); + CGContextRef thumbnailContextRef = CGBitmapContextCreate ( NULL, // void *data, + tw, // size_t width, + th, // size_t height, + 8, // size_t bitsPerComponent, + 4*tw, // size_t bytesPerRow, + colorspace, // CGColorSpaceRef colorspace, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big // CGBitmapInfo bitmapInfo + ); + CGRect tnrect; + // rotate the context + switch (imageOrientation) { + case 0: // no rotation + tnrect = CGRectMake( 0, 0, tw, th ); + break; + case 3: // rotate -90 + tnrect = CGRectMake( 0, 0, th, tw ); + CGContextTranslateCTM (thumbnailContextRef, 0.0f, float(th) ); + CGContextRotateCTM (thumbnailContextRef, -3.1415926f/2.0f); + break; + case 2: // rotate 180 + tnrect = CGRectMake( 0, 0, tw, th ); + CGContextTranslateCTM (thumbnailContextRef, float(tw), float(th) ); + CGContextRotateCTM (thumbnailContextRef, -3.1415926f); + break; + case 1: // rotate 90 + tnrect = CGRectMake( 0, 0, th, tw ); + CGContextTranslateCTM (thumbnailContextRef, float(tw), 0.0f ); + CGContextRotateCTM (thumbnailContextRef, 3.1415926f/2.0f); + break; + } + CGContextDrawImage ( thumbnailContextRef, tnrect, image.CGImage ); // draw image + + if( (unsigned int)profileId == gPlayerData.profileId ) + { + if (gPlayerData.thumbNail != nil) [gPlayerData.thumbNail release]; + gPlayerData.thumbNail = [[UIImage imageWithCGImage: CGBitmapContextCreateImage ( thumbnailContextRef )] retain]; + CGContextRelease(thumbnailContextRef); + CGColorSpaceRelease(colorspace); + + [busyLocal stopAnimating]; + busyLocal.hidden=YES; + [localThumbnailButton setBackgroundImage:gPlayerData.thumbNail forState: UIControlStateNormal ]; + //[localThumbnailButton setImage:gPlayerData.thumbNail forState: UIControlStateNormal ]; + + [localThumbnailButton setNeedsDisplay]; + } + else + { + if (gOpponentData.thumbNail != nil) [gOpponentData.thumbNail release]; + gOpponentData.thumbNail = [[UIImage imageWithCGImage: CGBitmapContextCreateImage ( thumbnailContextRef )] retain]; + CGContextRelease(thumbnailContextRef); + CGColorSpaceRelease(colorspace); + + [busyRemote stopAnimating]; + busyRemote.hidden=YES; + [remoteThumbnailButton setBackgroundImage:gOpponentData.thumbNail forState: UIControlStateNormal ]; + + [remoteThumbnailButton setNeedsDisplay]; + } + + free(data); +} + +@end + + +@implementation GameResultsController(Private) + +- (GHTTPBool) downloadImageCompletedCallbackRequest: (GHTTPRequest) request + result: (GHTTPResult) result + buffer: (char *) buffer + bufferLen: (GHTTPByteCount) bufferLen + profileId: (int) profileId +{ + if (discardCallbacks) return GHTTPFalse; + if(result != GHTTPSuccess) + { + NSLog(@"File Download: GHTTP Error: %d\n", result); + if( (unsigned int)profileId == gPlayerData.profileId ) + { + busyLocal.hidden=YES; + [busyLocal stopAnimating]; + } + else + { + busyRemote.hidden=YES; + [busyRemote stopAnimating]; + } + + ghttpCleanup(); + return GHTTPTrue; + } + // if picture is found, create a new thumbnail using the image in the buffer, then release the buffer + if( bufferLen ) + { + NSData * jpegImage = [NSData dataWithBytes:(const void *)buffer length: bufferLen]; + [self drawThumbnailImage: [UIImage imageWithData: jpegImage] forPlayer: profileId ]; + NSLog(@"Thumbnail downloaded for %d \n", profileId); + } + + + ghttpCleanup(); + [busyLocal stopAnimating]; + busyLocal.hidden=YES; + [busyRemote stopAnimating]; + busyRemote.hidden=YES; + + return GHTTPTrue; // frees the buffer +} + + + +- (void) sakeSearchForImageFileIdRequestCallback:(SAKE) sake request:(SAKERequest) request result:(SAKERequestResult) result inputData:(void *) inputData outputData:(void *) outputData +{ + if (discardCallbacks) return; + + if(result == SAKERequestResult_SUCCESS) + { + if ( ((SAKESearchForRecordsOutput*)outputData)->mNumRecords > 0) + { + int aProfileId, aFileId; + SAKEField* aField; + + NSLog(@"sakeSearchForImageFileIdRequestCallback get %d ids", ((SAKESearchForRecordsOutput*)outputData)->mNumRecords ); + for( int i = 0; i < ((SAKESearchForRecordsOutput*)outputData)->mNumRecords; i++ ) + { + aField = ( (SAKEField*) ((SAKESearchForRecordsOutput*)outputData)->mRecords[i]); + aProfileId = aField[0].mValue.mInt; + aFileId = aField[1].mValue.mInt; + + if ( (unsigned int)aProfileId == gPlayerData.profileId ) + { + gPlayerData.pictureFileId = aFileId; + [self downloadThumbnail: aFileId forPlayer: gPlayerData.profileId ]; + } + else + { + gOpponentData.pictureFileId = aFileId; + [self downloadThumbnail: aFileId forPlayer: gOpponentData.profileId ]; + } + + } + } + else + { + [busyLocal stopAnimating]; + [busyRemote stopAnimating]; + busyLocal.hidden=YES; + busyRemote.hidden=YES; + NSLog(@"No existing profiles\n"); + } + } + else + { + [busyLocal stopAnimating]; + [busyRemote stopAnimating]; + busyLocal.hidden=YES; + busyRemote.hidden=YES; + NSLog(@"Failed to search for this player record, error: %d\n", result); + } +} + + +- (void)determineWinner +{ + if ((localTime > 0) && (remoteTime > 0)) { + // Multi-player game. + if (localTime == remoteTime) + { + tieLabel.hidden = NO; + localWinnerLabel.hidden = YES; + remoteWinnerLabel.hidden = YES; + //localThumbnailButton.enabled = YES; + //remoteThumbnailButton.enabled = YES; + } + else if (localTime < remoteTime) + { + tieLabel.hidden = YES; + localWinnerLabel.hidden = NO; + remoteWinnerLabel.hidden = YES; + //localThumbnailButton.enabled = YES; + //remoteThumbnailButton.enabled = NO; + } + else + { + tieLabel.hidden = YES; + localWinnerLabel.hidden = YES; + remoteWinnerLabel.hidden = NO; + //localThumbnailButton.enabled = NO; + //remoteThumbnailButton.enabled = YES; + } + } +} + +/* +- (GHTTPBool) downloadImageCompletedCallbackRequest: (GHTTPRequest) request result: (GHTTPResult) result buffer: (char *) buffer bufferLen: (GHTTPByteCount) bufferLen +{ + if (discardCallbacks) return GHTTPFalse; + if(result != GHTTPSuccess) + { + NSLog(@"File Download: GHTTP Error: %d\n", result); + ghttpCleanup(); + return GHTTPTrue; + } + // if picture is found, create a new thumbnail using the image in the buffer, then release the buffer + if( bufferLen ) + { + NSData * jpegImage = [NSData dataWithBytes:(const void *)buffer length: bufferLen]; + [self drawThumbnailImage: [UIImage imageWithData: jpegImage] forProfileID: ]; + NSLog(@"Thumbnail downloaded\n"); + } + + + ghttpCleanup(); + [busy stopAnimating]; + return GHTTPTrue; // frees the buffer +} +*/ +@end + +void SakeSearchForImageFileIdRecordsCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ) +{ + if (discardCallbacks) return; + [(GameResultsController *)userData sakeSearchForImageFileIdRequestCallback: sake request: request result: result inputData: inputData outputData: outputData ]; + +} + + +GHTTPBool DownloadImageCompletedCallback(GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param) +{ + if (discardCallbacks) return GHTTPFalse; + + GHTTPBool res = [(GameResultsController *)( ((ServerParam*)param)->controller) downloadImageCompletedCallbackRequest: request + result: result + buffer: buffer + bufferLen: bufferLen + profileId: (int)( ((ServerParam*)param)->profileId) ]; + gsifree(param); + return res; +} diff --git a/Samples/TapRace/iphone/LeaderboardsController.h b/Samples/TapRace/iphone/LeaderboardsController.h new file mode 100644 index 00000000..e29446f1 --- /dev/null +++ b/Samples/TapRace/iphone/LeaderboardsController.h @@ -0,0 +1,66 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import + + +@interface LeaderboardsController : UIViewController +{ + IBOutlet UIButton* setPhotoButton; + IBOutlet UIButton* top100Button; + IBOutlet UIButton* mySpotButton; + IBOutlet UIButton* byLocationButton; + + IBOutlet UILabel* myTotalRaces; + IBOutlet UILabel* myRank; + IBOutlet UILabel* myTotalWins; + IBOutlet UILabel* myTotalLosses; + IBOutlet UILabel* myRatio; + IBOutlet UILabel* myBestTime; + IBOutlet UILabel* myStreak; + IBOutlet UILabel* myStreakLabel; + + + IBOutlet UILabel* mySPTotalRaces; + IBOutlet UILabel* mySPBestTime; + IBOutlet UILabel* mySPAvgTime; + + IBOutlet UIImageView* thumbNail; + + IBOutlet UIActivityIndicatorView* busy; + + UIImagePickerController* imagePickerController; + + int gUploadedFileId; + UIImage * newImage; +} + +@property (nonatomic, retain) UILabel* myTotalRaces; +@property (nonatomic, retain) UILabel* myRank; +@property (nonatomic, retain) UILabel* myTotalWins; +@property (nonatomic, retain) UILabel* myTotalLosses; +@property (nonatomic, retain) UILabel* myRatio; +@property (nonatomic, retain) UILabel* myBestTime; +@property (nonatomic, retain) UILabel* myStreak; +@property (nonatomic, retain) UILabel* myStreakLabel; +@property (nonatomic, retain) UILabel* mySPTotalRaces; +@property (nonatomic, retain) UILabel* mySPBestTime; +@property (nonatomic, retain) UILabel* mySPAvgTime; + +@property (nonatomic, retain) UIImageView* thumbNail; + + +- (IBAction)setPhotoButtonClicked: (id)sender; +- (IBAction)top100ButtonClicked: (id)sender; +- (IBAction)myBuddiesButtonClicked: (id)sender; +- (IBAction)byLocationButtonClicked: (id)sender; +- (IBAction)showInfo; + +- (void) testForExistingImageRecord; +- (void) getImageFromProfile; +- (void) drawAndUploadImage: (UIImage*)image; +- (void) drawThumbnailFromImage: (UIImage*)image; +- (void) delayedDraw; +- (void) delayedRank; + +@end diff --git a/Samples/TapRace/iphone/LeaderboardsController.mm b/Samples/TapRace/iphone/LeaderboardsController.mm new file mode 100644 index 00000000..520a8888 --- /dev/null +++ b/Samples/TapRace/iphone/LeaderboardsController.mm @@ -0,0 +1,786 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "LeaderboardsController.h" +#import "LoginController.h" +#import "MyCLController.h" +#import "AppDelegate.h" +#import "Utility.h" +#import "sake/sakeRequest.h" +#import + +//#define getLocalIntegerStat(key) [(NSNumber*)[gPlayerData.playerStatsData objectForKey: key ] intValue] +//#define getLocalStringStat(key) [(NSString*)[gPlayerData.playerStatsData objectForKey: key ]] + +// callback prototypes +// http stuff to upload and download image +void postCallback( GHTTPRequest request, int bytesPosted, int totalBytes, int objectsPosted, int totalObjects, void * param ); +GHTTPBool UploadCompletedCallback(GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param); +GHTTPBool DownloadCompletedCallback(GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param); +// sake stuff to access and create persistent server storage +void SakeCreateRecordCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ); +void SakeUpdateRecordCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ); +void SakeTestRecordCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ); +void SakeSearchForRecordsCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ); + +static SAKEField fields[2]; + +static bool discardCallbacks = true; + +// Exists for the simulator, not for the device: #import +@interface LeaderboardsController(Private) + +@end + + +@implementation LeaderboardsController + +@synthesize myTotalRaces; +@synthesize myRank; +@synthesize myTotalWins; +@synthesize myTotalLosses; +@synthesize myRatio; +@synthesize myBestTime; +@synthesize myStreak; +@synthesize myStreakLabel; +@synthesize mySPTotalRaces; +@synthesize mySPBestTime; +@synthesize mySPAvgTime; +@synthesize thumbNail; + + +- (void)dealloc +{ + discardCallbacks = true; + if (imagePickerController != nil) + { + [imagePickerController release]; + imagePickerController = nil; + } + + [thumbNail release]; + [setPhotoButton release]; + [top100Button release]; + [mySpotButton release]; + [byLocationButton release]; + [myTotalRaces release]; + [myTotalWins release]; + [myTotalLosses release]; + [myRatio release]; + [myBestTime release]; + [myStreak release]; + [mySPTotalRaces release]; + [mySPBestTime release]; + [mySPAvgTime release]; + [busy release]; + [super dealloc]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation +{ + // Return YES for supported orientations + return interfaceOrientation == UIInterfaceOrientationPortrait; +} + +- (void)viewWillAppear: (BOOL)animated +{ + [super viewWillAppear: animated]; + + [appDelegate getMyLeaderPosition ]; + + NSString* aTitle = [NSString stringWithFormat: @"%@'s Profile", gPlayerData.uniquenick ]; + self.title = aTitle; //[leader.name copy]; // index of currently displayed profile + + // Hide Buddies leaderboard button if no buddies + int numBuddies; + LoginController* loginController = (LoginController*)[appDelegate findExistingControllerOfType: [LoginController class]]; + GPResult res = gpGetNumBuddies( [loginController getGPConnectionPtr], &numBuddies ); + + if ( (res == GP_NO_ERROR) && (numBuddies == 0)) + mySpotButton.hidden= YES; + else + mySpotButton.hidden = NO; + + // disable Location button if GPS is not available + NSString *modelType = [UIDevice currentDevice].model; + if( ! [modelType isEqualToString: (NSString *) @"iPod touch" ] ) + { + // disable Location button on iPhone if GPS is not available + if( [MyCLController sharedInstance].locationManager.locationServicesEnabled ) + byLocationButton.hidden = NO; + else + byLocationButton.hidden = YES; + } + + setPhotoButton.hidden = ![UIImagePickerController isSourceTypeAvailable: UIImagePickerControllerSourceTypePhotoLibrary]; + + // hide buddy list button for v1 + mySpotButton.hidden = YES; + + int matchesPlayed = getLocalIntegerStat(matchGamesPlayedKey); + myTotalRaces.text = [NSString stringWithFormat: @"%d", matchesPlayed ]; + mySPTotalRaces.text = [NSString stringWithFormat: @"%d", getLocalIntegerStat(singlePlayerGamesPlayedKey)]; + + int wins = getLocalIntegerStat(matchWonKey); + int loss = getLocalIntegerStat(matchLossKey); + + myTotalWins.text = [NSString stringWithFormat: @"%d", wins]; + myTotalLosses.text = [NSString stringWithFormat: @"%d", loss ]; + + if( matchesPlayed > 0 ) + { + myRatio.text = [NSString stringWithFormat: @"%d\%%", (wins*100)/matchesPlayed ]; + } + else + { + myRatio.text = [NSString stringWithString: @"0"]; + } + + int bestRaceTime = getLocalIntegerStat(matchBestTimeKey); + if( (bestRaceTime <= 0) || (bestRaceTime >= INITIAL_BEST_TIME) ) + { + myBestTime.text = [NSString stringWithString: @"none"]; + } + else + { + SetTimeLabel( myBestTime, bestRaceTime); + } + + //int spBestTime = [(NSNumber*)[gPlayerData.playerStatsData objectForKey: singlePlayerBestTimeKey] intValue]; + int spBestRaceTime = getLocalIntegerStat(singlePlayerBestTimeKey); + int spAvgPlayTime = getLocalIntegerStat(singlePlayerAverageTimeKey); + + if( (spBestRaceTime <= 0) || (spBestRaceTime >= INITIAL_BEST_TIME) ) + { + mySPBestTime.text = [NSString stringWithString: @"none"]; + mySPAvgTime.text = [NSString stringWithString: @"none"]; + } + else + { + SetTimeLabel( mySPBestTime, spBestRaceTime ); + SetTimeLabel(mySPAvgTime, spAvgPlayTime ); + } + + // determine whether to display win, loss or draw streak + int tmpStreak = getLocalIntegerStat(matchDrawStreakKey); + NSMutableString* tmpStreakLabel = @"Draw Streak:"; + + if( tmpStreak == 0 ) + { + tmpStreak = getLocalIntegerStat(matchLossStreakKey); + tmpStreakLabel = @"Loss Streak:"; + + if( tmpStreak == 0 ) + { + tmpStreak = getLocalIntegerStat(matchWinStreakKey); + tmpStreakLabel = @"Win Streak:"; + } + } + + myStreak.text = [NSString stringWithFormat: @"%d", tmpStreak ]; + myStreakLabel.text = tmpStreakLabel; + + discardCallbacks = false; + + // display current rank + if( gPlayerData.rank > 0 ) + myRank.text = [NSString stringWithFormat: @"#%d", gPlayerData.rank ]; + else + { + // go get rank retrieved from server + [self performSelector:@selector(delayedRank) withObject:nil afterDelay:1.0f]; + } + + // if coming back from picking a new image... + if (newImage != nil ) + { + // do a delayed request to allow this view to draw, then upload the image + [busy startAnimating]; + [self performSelector:@selector(delayedDraw) withObject:nil afterDelay:0.02f]; + } + else if (gPlayerData.thumbNail != nil) // if no thumbnail, just leave as the default 'No Photo' + { + [busy stopAnimating]; + [self.thumbNail setImage: gPlayerData.thumbNail]; + } + else + { + // check to see if there is a player record + [busy startAnimating]; + [self getImageFromProfile]; + } +} + +// Display the About page + - (IBAction)showInfo + { + [ appDelegate showAboutInfo: self ]; + } + +// Display the iPhone Photo Album to select a picture +- (IBAction)setPhotoButtonClicked: (id)sender +{ + imagePickerController = [[UIImagePickerController alloc] init]; + imagePickerController.sourceType = UIImagePickerControllerSourceTypePhotoLibrary; + imagePickerController.delegate = self; +// self.navigationController.navigationBarHidden = YES; +// [self.navigationController pushViewController: imagePickerController animated: NO]; + [self presentModalViewController:imagePickerController animated:YES]; + //[imagePickerController release]; + +} + +// Display the table to Top 30 leaders +- (IBAction)top100ButtonClicked: (id)sender +{ + [appDelegate showLeadersByTime]; +} + +- (IBAction)myBuddiesButtonClicked: (id)sender +{ + [appDelegate showLeadersByBuddies]; +} + +- (IBAction)byLocationButtonClicked: (id)sender +{ + [appDelegate showLeadersByLocation]; +} + +- (void) getImageFromProfile +{ + SAKERequest request; + SAKEStartRequestResult startRequestResult; + static SAKESearchForRecordsInput sfr_input; + static char * fieldNames[] = { (char *) "picture" }; + int ownerids[] = { gPlayerData.profileId }; + + sfr_input.mTableId = (char *) "Profiles"; + sfr_input.mFieldNames = fieldNames; + sfr_input.mNumFields = 1; + sfr_input.mSort = (char *) "recordid asc"; + sfr_input.mOffset = 0; + sfr_input.mMaxRecords = 1; + sfr_input.mOwnerIds = ownerids; + sfr_input.mNumOwnerIds = 1; + + request = sakeSearchForRecords(appDelegate.sake, &sfr_input, SakeSearchForRecordsCallback, self ); + if(!request) + { + startRequestResult = sakeGetStartRequestResult(appDelegate.sake); + NSLog(@"Failed to start search for records request: %d\n", startRequestResult); + } + else + { + NSLog(@"Searching for player record\n"); + } +} + + +- (void) testForExistingImageRecord +{ + SAKERequest request; + SAKEStartRequestResult testRequestResult; + static SAKEGetMyRecordsInput gmr_input; + static char *fieldnames[] = { (char *) "recordId" }; + gmr_input.mTableId = (char *) "Profiles"; + gmr_input.mFieldNames = fieldnames; + gmr_input.mNumFields = 1; + + request = sakeGetMyRecords(appDelegate.sake, &gmr_input, SakeTestRecordCallback, self); + if(!request) + { + testRequestResult = sakeGetStartRequestResult(appDelegate.sake); + NSLog(@"Failed to start test for record request: %d\n", testRequestResult); + [busy stopAnimating]; + } + else + { + NSLog(@"Testing for existing image record\n"); + } +} + +- (void) drawAndUploadImage: (UIImage*)image +{ + GHTTPPost post; + GHTTPRequest request; + gsi_char url[SAKE_MAX_URL_LENGTH]; + + int imgh = image.size.height; + int imgw = image.size.width; + + NSLog(@"drawAndUploadImage full image height=%d width=%d", imgh, imgw); + + [busy startAnimating]; + [self drawThumbnailFromImage: image]; + + ghttpStartup(); + + // get an upload url + if(sakeGetFileUploadURL(appDelegate.sake, url)) + { + NSLog(@"Upload URL: %s\n", url); + + // upload a file + post = ghttpNewPost(); + ghttpPostSetCallback(post, postCallback, self); + + // get a jpeg version of the image + NSLog(@"drawAndUploadImage full image height=%d width=%d", gPlayerData.thumbNail.size.height, gPlayerData.thumbNail.size.width); + NSData * jpeg = UIImageJPEGRepresentation ( gPlayerData.thumbNail, 1.0f ); + ghttpPostAddFileFromMemory(post, "picture.image", (const char *)[jpeg bytes], [jpeg length], "picture.image", NULL); + + request = ghttpPostEx(url, NULL, post, GHTTPFalse, GHTTPTrue, NULL, UploadCompletedCallback, self); + + if(request == -1) + { + NSLog(@"Error starting file upload\n"); + } + else + { + [appDelegate startThinkTimer]; + return; // wait for callback before cleaning up ghttp + } + } + else + { + NSLog(@"Failed to get upload url!\n"); + } + + ghttpCleanup(); + [busy stopAnimating]; +} + +// Resize the downloaded image and save in gPlayerData.fullsizeImage to represent the profile size +// Resize the fullsizeimage to and save in gPlayerData.thumbnail to represent the leaderboard thumbnail size +// Then draw thumbnail +- (void) drawThumbnailFromImage: (UIImage*)image +{ + + // get the current colorspace + int imgh = image.size.height; + int imgw = image.size.width; + + NSLog(@"drawThumbnailFromImage h=%d w=%d", imgh, imgw); + CGColorSpaceRef colorspace = CGColorSpaceCreateDeviceRGB( ); + void * data = calloc(imgh, 4*imgw); + CGContextRef bmpContextRef = CGBitmapContextCreate ( data, // void *data, + imgw, // size_t width, + imgh, // size_t height, + 8, // size_t bitsPerComponent, + 4*imgw, // size_t bytesPerRow, + colorspace, // CGColorSpaceRef colorspace, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big // CGBitmapInfo bitmapInfo + ); + CGRect fsrect = CGRectMake( 0, 0, imgw, imgh ); + CGContextDrawImage ( bmpContextRef, fsrect, image.CGImage ); // draw (and resize) image + if (gPlayerData.fullsizeImage != nil) [gPlayerData.fullsizeImage release]; + gPlayerData.fullsizeImage = [[UIImage imageWithCGImage: CGBitmapContextCreateImage ( bmpContextRef )] retain]; + CGContextRelease(bmpContextRef); + free(data); + + // now redraw into thumbnail + int imageOrientation = image.imageOrientation; + int th,tw; + float hscale = ((float)imgh)/float(THUMBNAIL_HEIGHT); + float wscale = ((float)imgw)/float(THUMBNAIL_WIDTH); + if (hscale > wscale) { + // scale to height + th = THUMBNAIL_HEIGHT; + tw = (int)((float)imgw/hscale); + } else { + tw = THUMBNAIL_WIDTH; + th = (int)((float)imgh/wscale); + } + data = calloc(th, 4*tw); + CGContextRef thumbnailContextRef = CGBitmapContextCreate ( NULL, // void *data, + tw, // size_t width, + th, // size_t height, + 8, // size_t bitsPerComponent, + 4*tw, // size_t bytesPerRow, + colorspace, // CGColorSpaceRef colorspace, + kCGImageAlphaPremultipliedLast | kCGBitmapByteOrder32Big // CGBitmapInfo bitmapInfo + ); + CGRect tnrect; + // rotate the context + switch (imageOrientation) { + case 0: // no rotation + tnrect = CGRectMake( 0, 0, tw, th ); + break; + case 3: // rotate -90 + tnrect = CGRectMake( 0, 0, th, tw ); + CGContextTranslateCTM (thumbnailContextRef, 0.0f, float(th) ); + CGContextRotateCTM (thumbnailContextRef, -3.1415926f/2.0f); + break; + case 2: // rotate 180 + tnrect = CGRectMake( 0, 0, tw, th ); + CGContextTranslateCTM (thumbnailContextRef, float(tw), float(th) ); + CGContextRotateCTM (thumbnailContextRef, -3.1415926f); + break; + case 1: // rotate 90 + tnrect = CGRectMake( 0, 0, th, tw ); + CGContextTranslateCTM (thumbnailContextRef, float(tw), 0.0f ); + CGContextRotateCTM (thumbnailContextRef, 3.1415926f/2.0f); + break; + } + CGContextDrawImage ( thumbnailContextRef, tnrect, image.CGImage ); // draw image + if (gPlayerData.thumbNail != nil) [gPlayerData.thumbNail release]; + gPlayerData.thumbNail = [[UIImage imageWithCGImage: CGBitmapContextCreateImage ( thumbnailContextRef )] retain]; + + imgh = gPlayerData.thumbNail.size.height; + imgw = gPlayerData.thumbNail.size.width; + NSLog(@"drawThumbnailFromImage thumbnail h=%d w=%d", imgh, imgw); + + CGContextRelease(thumbnailContextRef); + CGColorSpaceRelease(colorspace); + free(data); + + [busy stopAnimating]; + [self.thumbNail setImage: gPlayerData.thumbNail]; + [thumbNail setNeedsDisplay]; +} + +// called after a delay to upload a new image from the image picker... +// gives the mainloop a chance to switch views and render the leaders menu again before the upload starts +- (void)delayedDraw +{ + int imgh = newImage.size.height; + int imgw = newImage.size.width; + + NSLog(@"delayedDraw full image height=%d width=%d", imgh, imgw); + + [self drawAndUploadImage: newImage]; + [newImage release]; + newImage = nil; +} + +// called after a delay to show rank +// gives the mainloop a chance for callback to set server value for rank +- (void)delayedRank +{ + if( gPlayerData.rank != 0 ) + myRank.text = [NSString stringWithFormat: @"#%d", gPlayerData.rank ]; +} + +@end + + +@implementation LeaderboardsController(Private) + +// +//- (void) drawAndUploadImage: (UIImage*)image +//{ +// GHTTPPost post; +// GHTTPRequest request; +// gsi_char url[SAKE_MAX_URL_LENGTH]; +// +// [busy startAnimating]; +// [self drawThumbnailFromImage: image]; +// +// ghttpStartup(); +// +// // get an upload url +// if(sakeGetFileUploadURL(appDelegate.sake, url)) { +// printf("Upload URL: %s\n", url); +// // upload a file +// post = ghttpNewPost(); +// ghttpPostSetCallback(post, postCallback, self); +// // get a jpeg version of the image +// NSData * jpeg = UIImageJPEGRepresentation ( gPlayerData.thumbnail, 1.0f ); +// ghttpPostAddFileFromMemory(post, "picture.image", (const char *)[jpeg bytes], [jpeg length], "picture.image", NULL); +// +// request = ghttpPostEx(url, NULL, post, GHTTPFalse, GHTTPTrue, NULL, UploadCompletedCallback, self); +// +// if(request == -1) +// { +// printf("Error starting file upload\n"); +// } else { +// [appDelegate startThinkTimer]; +// return; // wait for callback before cleaning up ghttp +// } +// } else { +// printf("Failed to get upload url!\n"); +// } +// +// ghttpCleanup(); +// [busy stopAnimating]; +//} + +- (void)imagePickerController: (UIImagePickerController*)picker didFinishPickingImage: (UIImage*)image editingInfo: (NSDictionary*)editingInfo +{ +// [self.navigationController popToViewController: self animated: NO]; +// self.navigationController.navigationBarHidden = NO; + newImage = [image retain]; // set the new image field + [self dismissModalViewControllerAnimated:YES]; + [imagePickerController release]; + imagePickerController = nil; +} + +- (void)imagePickerControllerDidCancel:(UIImagePickerController *)picker +{ + [self dismissModalViewControllerAnimated:YES]; + [imagePickerController release]; + imagePickerController = nil; +// [self.navigationController popToViewController: self animated: YES]; +// self.navigationController.navigationBarHidden = NO; +} + +- (void) postCallBackRequest: (GHTTPRequest) request bytesPosted: (int) bytesPosted totalBytes: (int) totalBytes objectsPosted: (int) objectsPosted totalObjects: (int) totalObjects { + // progress of post +} + +- (void) setupRecordRequest: (char**) tableId fields: (SAKEField**) fieldsPtr fieldCount: (int*)numFields { + *tableId = (char *) "Profiles"; + fields[0].mName = (char *) "nick"; + fields[0].mType = SAKEFieldType_ASCII_STRING; + fields[0].mValue.mAsciiString = (char*) [gPlayerData.uniquenick cStringUsingEncoding: NSASCIIStringEncoding]; // player nick goes in here + fields[1].mName = (char *) "picture"; + fields[1].mType = SAKEFieldType_INT; + fields[1].mValue.mInt = gUploadedFileId; // fileid of image + *fieldsPtr = fields; + *numFields = 2; +} + +- (void) updateImageToProfile: (int)recordId { + SAKERequest request; + SAKEStartRequestResult startRequestResult; + static SAKEUpdateRecordInput ur_input; + + [self setupRecordRequest: &ur_input.mTableId fields: &ur_input.mFields fieldCount: &ur_input.mNumFields]; + ur_input.mRecordId = recordId; + request = sakeUpdateRecord(appDelegate.sake, &ur_input, SakeUpdateRecordCallback, self); + if(!request) + { + startRequestResult = sakeGetStartRequestResult(appDelegate.sake); + NSLog(@"Failed to start request: %d\n", startRequestResult); + [busy stopAnimating]; + } else { + NSLog(@"Updating image to profile\n"); + } +} + +- (void) addImageToProfile { + SAKERequest request; + SAKEStartRequestResult startRequestResult; + static SAKECreateRecordInput cr_input; + + [self setupRecordRequest: &cr_input.mTableId fields: &cr_input.mFields fieldCount: &cr_input.mNumFields]; + request = sakeCreateRecord(appDelegate.sake, &cr_input, SakeCreateRecordCallback, self); + if(!request) + { + startRequestResult = sakeGetStartRequestResult(appDelegate.sake); + NSLog(@"Failed to start request: %d\n", startRequestResult); + } else { + NSLog(@"Adding image to profile\n"); + } +} + +- (void) downloadImageToThumbnail: (int) fileId +{ + GHTTPRequest request; + gsi_char url[SAKE_MAX_URL_LENGTH]; + + ghttpStartup(); + if (sakeGetFileDownloadURL(appDelegate.sake, fileId, url)) + { + NSLog(@"Download image URL: %s\n", url); + + // download the file + request = ghttpGetEx(url, NULL, NULL, 0, NULL, GHTTPFalse, GHTTPFalse, NULL, DownloadCompletedCallback, self); + if (request == -1) + { + NSLog(@"Error starting image file download\n"); + [busy stopAnimating]; + } else { + [appDelegate startThinkTimer]; + return; + } + } else { + NSLog(@"Failed to get download url!\n"); + [busy stopAnimating]; + } + ghttpCleanup(); +} + +- (void) sakeCreateRecordRequestCallback:(SAKE) sake request:(SAKERequest) request result:(SAKERequestResult) result inputData:(void *) inputData outputData:(void *) outputData +{ + if (discardCallbacks) return; + if(result != SAKERequestResult_SUCCESS) + { + NSLog(@"Failed to add image to profile, error: %d\n", result); + } else { + NSLog(@"Image added to profile\n"); + } + [busy stopAnimating]; +} + +- (void) sakeUpdateRecordRequestCallback:(SAKE) sake request:(SAKERequest) request result:(SAKERequestResult) result inputData:(void *) inputData outputData:(void *) outputData +{ + if (discardCallbacks) return; + if(result != SAKERequestResult_SUCCESS) + { + NSLog(@"Failed to update image to profile, error: %d\n", result); + } else { + NSLog(@"Image updated to profile\n"); + } + [busy stopAnimating]; +} + +- (void) sakeTestRecordRequestCallback:(SAKE) sake request:(SAKERequest) request result:(SAKERequestResult) result inputData:(void *) inputData outputData:(void *) outputData +{ + if (discardCallbacks) return; + if(result == SAKERequestResult_SUCCESS) { + if ( ((SAKEGetMyRecordsOutput*)outputData)->mNumRecords > 0) + { + + [self updateImageToProfile: ((SAKEGetMyRecordsOutput*)outputData)->mRecords[0]->mValue.mInt]; + } else { + [self addImageToProfile ]; + } + } else { + NSLog(@"Failed to test for image record, error: %d\n", result); + } + [busy stopAnimating]; +} + +- (void) sakeSearchForRecordsRequestCallback:(SAKE) sake request:(SAKERequest) request result:(SAKERequestResult) result inputData:(void *) inputData outputData:(void *) outputData +{ + if (discardCallbacks) return; + + if(result == SAKERequestResult_SUCCESS) + { + if ( ((SAKESearchForRecordsOutput*)outputData)->mNumRecords > 0) + { + gPlayerData.pictureFileId = ((SAKEGetMyRecordsOutput*)outputData)->mRecords[0]->mValue.mInt; + [self downloadImageToThumbnail: gPlayerData.pictureFileId ]; + } + else + { + [busy stopAnimating]; + NSLog(@"No existing profile for this player\n"); + } + } + else + { + [busy stopAnimating]; + NSLog(@"Failed to search for this player record, error: %d\n", result); + } +} + +- (GHTTPBool) uploadCompletedCallbackRequest: (GHTTPRequest) request result: (GHTTPResult) result buffer: (char *) buffer bufferLen: (GHTTPByteCount) bufferLen +{ + SAKEFileResult fileResult; + gsi_bool gUploadResult; + gUploadResult = gsi_false; + + if (discardCallbacks) return GHTTPFalse; + [busy stopAnimating]; + if(result != GHTTPSuccess) + { + NSLog(@"File Upload: GHTTP Error: %d\n", result); + ghttpCleanup(); + return GHTTPTrue; + } + + if(!sakeGetFileResultFromHeaders(ghttpGetHeaders(request), &fileResult)) + { + NSLog(@"File Upload: Failed to find Sake-File-Result header\n"); + ghttpCleanup(); + return GHTTPTrue; + } + + if(fileResult != SAKEFileResult_SUCCESS) + { + NSLog(@"File Upload: SakeFileResult != success: %d\n", fileResult); + ghttpCleanup(); + return GHTTPTrue; + } + + if(!sakeGetFileIdFromHeaders(ghttpGetHeaders(request), &gUploadedFileId)) + { + NSLog(@"File Upload: Unable to parse Sake-File-Id header\n"); + ghttpCleanup(); + return GHTTPTrue; + } + + gPlayerData.pictureFileId = gUploadedFileId; // save new picture file id + + NSLog(@"File Upload: Uploaded fileId: %d\n", gUploadedFileId); + gUploadResult = gsi_true; + ghttpCleanup(); + + // see if record already existed + [busy startAnimating]; + [self testForExistingImageRecord ]; + + return GHTTPTrue; +} + +- (GHTTPBool) downloadCompletedCallbackRequest: (GHTTPRequest) request result: (GHTTPResult) result buffer: (char *) buffer bufferLen: (GHTTPByteCount) bufferLen +{ + if (discardCallbacks) return GHTTPFalse; + if(result != GHTTPSuccess) + { + NSLog(@"File Download: GHTTP Error: %d\n", result); + ghttpCleanup(); + return GHTTPTrue; + } + // if picture is found, create a new thumbnail using the image in the buffer, then release the buffer + if( bufferLen ) + { + NSData * jpegImage = [NSData dataWithBytes:(const void *)buffer length: bufferLen]; + [self drawThumbnailFromImage: [UIImage imageWithData: jpegImage]]; + NSLog(@"Thumbnail downloaded\n"); + } + + + ghttpCleanup(); + [busy stopAnimating]; + return GHTTPTrue; // frees the buffer +} + + +@end + +// callbacks +void postCallback( GHTTPRequest request, int bytesPosted, int totalBytes, int objectsPosted, int totalObjects, void * param ) +{ + if (discardCallbacks) return; + [(LeaderboardsController *)param postCallBackRequest: request bytesPosted: bytesPosted totalBytes: totalBytes objectsPosted: objectsPosted totalObjects: totalObjects ]; +} + +GHTTPBool UploadCompletedCallback(GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param) +{ + if (discardCallbacks) return GHTTPFalse; + return [(LeaderboardsController *)param uploadCompletedCallbackRequest: request result: result buffer: buffer bufferLen: bufferLen]; +} + +GHTTPBool DownloadCompletedCallback(GHTTPRequest request, GHTTPResult result, char * buffer, GHTTPByteCount bufferLen, void * param) +{ + if (discardCallbacks) return GHTTPFalse; + return [(LeaderboardsController *)param downloadCompletedCallbackRequest: request result: result buffer: buffer bufferLen: bufferLen]; +} + +void SakeCreateRecordCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ) +{ + if (discardCallbacks) return; + [(LeaderboardsController *)userData sakeCreateRecordRequestCallback: sake request: request result: result inputData: inputData outputData: outputData ]; +} + +void SakeUpdateRecordCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ) +{ + if (discardCallbacks) return; + [(LeaderboardsController *)userData sakeUpdateRecordRequestCallback: sake request: request result: result inputData: inputData outputData: outputData ]; +} + +void SakeTestRecordCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ) +{ + if (discardCallbacks) return; + [(LeaderboardsController *)userData sakeTestRecordRequestCallback: sake request: request result: result inputData: inputData outputData: outputData ]; +} + +void SakeSearchForRecordsCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ) +{ + if (discardCallbacks) return; + [(LeaderboardsController *)userData sakeSearchForRecordsRequestCallback: sake request: request result: result inputData: inputData outputData: outputData ]; +} + diff --git a/Samples/TapRace/iphone/LeadersByLocationController.h b/Samples/TapRace/iphone/LeadersByLocationController.h new file mode 100644 index 00000000..ccac5b45 --- /dev/null +++ b/Samples/TapRace/iphone/LeadersByLocationController.h @@ -0,0 +1,27 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import +#import + +@interface LeadersByLocationController : UIViewController +{ + NSString * myName; + NSTimer* updateTimer; + + IBOutlet UIWebView* webView; + IBOutlet MKMapView *mapView; + IBOutlet UIActivityIndicatorView *spinner; + + BOOL isCurrentlyUpdating; + UIAlertView* alertView; +} + +@property (nonatomic, retain) UIActivityIndicatorView *spinner; +@property (nonatomic, retain) NSString * myName; + +- (IBAction)showInfo; + +- (void)searchLeaderboardsByLocation; + +@end diff --git a/Samples/TapRace/iphone/LeadersByLocationController.mm b/Samples/TapRace/iphone/LeadersByLocationController.mm new file mode 100644 index 00000000..f05fe949 --- /dev/null +++ b/Samples/TapRace/iphone/LeadersByLocationController.mm @@ -0,0 +1,465 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "LeadersByLocationController.h" +#import "AppDelegate.h" +#import "Utility.h" +#include "atlas_taprace_v2.h" +#import "UserStatsController.h" + +#define LOCATION_DEGREES_RADIUS 1.1f +#define METER_RADIUS_FOR_SELECT (6000.0f*LOCATION_DEGREES_RADIUS) // approximate meters radius of map/10 +#define MAP_ZOOM 7 + +// structure to hold the buddy info +@interface buddy : NSObject +{ + NSString * name; + CLLocationDegrees latitude; + CLLocationDegrees longitude; +} + +@property (nonatomic, copy) NSString * name; +@property CLLocationDegrees latitude; +@property CLLocationDegrees longitude; + +@end + +//************************************** +@implementation buddy +@synthesize name, latitude, longitude; +@end + + +void SearchForLocationRecordsCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ); +static const char PLAYER_STATS_TABLE[] = "PlayerStats_v" STRINGIZE(ATLAS_RULE_SET_VERSION); + + +//************************************** +@implementation LeadersByLocationController + +@synthesize spinner, myName; + +- (void)dealloc +{ + [webView release]; + [spinner release]; + [updateTimer invalidate]; + [updateTimer release]; + [super dealloc]; +} + +//************************************** +- (id)initWithNibName:(NSString *)nibNameOrNil bundle:(NSBundle *)nibBundleOrNil { + if ( (self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil]) ) { + self->isCurrentlyUpdating = NO; + self.myName = [gPlayerData.uniquenick copy]; + } + return self; +} + +//************************************** +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + // Return YES for supported orientations + return (interfaceOrientation == UIInterfaceOrientationPortrait); +} + +//************************************** +- (void)updateTime: (NSTimer*)timer; +{ + // try to issue javascript to grab the latest lat/long + NSString * touched = [webView stringByEvaluatingJavaScriptFromString: @"touched"]; + if ([touched intValue]) { + [webView stringByEvaluatingJavaScriptFromString: @"touched = 0;"]; + NSString * lastLat = [webView stringByEvaluatingJavaScriptFromString: @"lastLat"]; + NSString * lastLon = [webView stringByEvaluatingJavaScriptFromString: @"lastLon"]; + + CLLocation * loc = [[CLLocation alloc] initWithLatitude: [lastLat floatValue] longitude: [lastLon floatValue]]; + int budindex = -1; + + // locate the closest individual + CLLocationDistance distance = 99999.99f; + for (unsigned int i = 0; i < [gLeaderboardData count]; i++) + { + LeaderboardObject * nextbuddy = (LeaderboardObject*)[gLeaderboardData objectAtIndex: i]; + CLLocationDistance buddyDist = [loc getDistanceFrom: nextbuddy.playerLocation]; + if (buddyDist < distance) { + budindex = i; + distance = buddyDist; + } + } + // if not within 1000 meters, ignore the touch + if ((distance < METER_RADIUS_FOR_SELECT) && (budindex >= 0)) { + [appDelegate showUser: budindex]; // show the user profile + } + } +} + +//************************************** +- (MKAnnotationView *)mapView:(MKMapView *)aMapView viewForAnnotation:(id )annotation +{ + MKPinAnnotationView *av = (MKPinAnnotationView *)[aMapView dequeueReusableAnnotationViewWithIdentifier:@"myID"]; + if ( nil == av ) + av = [[MKPinAnnotationView alloc] initWithAnnotation:annotation reuseIdentifier:@"myId"]; + + UIButton *button = [UIButton buttonWithType:UIButtonTypeDetailDisclosure]; + av.rightCalloutAccessoryView = button; + av.canShowCallout = YES; + return [av autorelease]; +} + + +//************************************** +- (void)mapView:(MKMapView *)mapView annotationView:(MKAnnotationView *)view calloutAccessoryControlTapped:(UIControl *)control +{ + gLeaderboardProfileIndex = 0; + + // get the selected uniquenick and set user to display from list + NSString* selectedLeader = [[ view annotation] title ]; + + for( unsigned int index = 0; index < [gLeaderboardData count]; index++ ) + { + if( [ selectedLeader isEqualToString: ((LeaderboardObject*)[gLeaderboardData objectAtIndex: index ]).name ] ) + { + gLeaderboardProfileIndex = index; + break; + } + } + + [appDelegate pushNewControllerOfType: [UserStatsController class] nib: @"UserStats"]; +} + + +//************************************** +- (void)viewDidLoad { + unsigned int i; + CLLocationDegrees myLat, buddyLat, myLon, buddyLon; + + myLat = gPlayerData.playerLocation.coordinate.latitude; + myLon = gPlayerData.playerLocation.coordinate.longitude; + NSLog(@"myLat %f myLon %f", myLat, myLon ); + + // calculate area to display + // All leaders with no position are set to my position + for( i = 0; i < [gLeaderboardData count]; i++ ) + { + LeaderboardObject * nextbuddy = (LeaderboardObject*)[gLeaderboardData objectAtIndex: i]; + + buddyLat = nextbuddy.playerLocation.coordinate.latitude; + buddyLon = nextbuddy.playerLocation.coordinate.longitude; + if( buddyLat == 0.0 && buddyLon == 0.0 ) + { + NSLog(@"Unknown location for %s\n", nextbuddy.name ); + nextbuddy.playerLocation.coordinate.latitude = myLat; + nextbuddy.playerLocation.coordinate.longitude = myLon; + continue; + } + + NSLog(@"bddy Lat %lf Lon %lf", buddyLat, buddyLon ); + } + + MKCoordinateSpan span = MKCoordinateSpanMake( LOCATION_DEGREES_RADIUS, LOCATION_DEGREES_RADIUS); + MKCoordinateRegion location = { gPlayerData.playerLocation.coordinate, span }; + [mapView setRegion:location]; + + // set pins for each player + [mapView addAnnotations:gLeaderboardData]; +} + +// Display the About page +- (IBAction)showInfo +{ + [ appDelegate showAboutInfo: self ]; +} + + +/* +-(void)viewDidLoadOld { + + [spinner startAnimating]; + // TODO: Parse and save lat/long + NSMutableString *update = [[[NSMutableString alloc] init] autorelease]; + [update appendString:@"\n\n\n\n
\n"]; + [update appendString:@"\n"]; + + [webView loadHTMLString:update baseURL: nil]; + + if (alertView != nil) { + // remove the previous alert + [alertView dismissWithClickedButtonIndex: alertView.cancelButtonIndex+1 animated: NO]; + [alertView release]; + alertView = nil; + } + + if ([gLeaderboardData count] == 0) { + alertView = [[UIAlertView alloc] initWithTitle: nil message: @"Searching for nearby players" delegate: self cancelButtonTitle: nil otherButtonTitles: nil]; + [alertView show]; + return; // don't look for tagTouch events + } + // kick off a timer to test for tagTouch events + if (updateTimer != nil) { + [updateTimer invalidate]; + } + updateTimer = [NSTimer scheduledTimerWithTimeInterval: 0.01 target: self selector: @selector(updateTime:) userInfo: nil repeats: YES]; + + [spinner stopAnimating]; +} +*/ + +- (void)alertView: (UIAlertView*)alert didDismissWithButtonIndex: (NSInteger)buttonIndex +{ + if ([gLeaderboardData count] == 0) + { + // same as exit + [self.navigationController popViewControllerAnimated: YES]; + } +} + +-(void)newError:(NSString *)text { + alertView = [[UIAlertView alloc] initWithTitle: nil message: @"Error finding location" delegate: self cancelButtonTitle: @"OK" otherButtonTitles: nil]; + [alertView show]; +} + +- (void) searchLeaderboardsByLocation +{ + static SAKESearchForRecordsInput input; + SAKERequest request; + static char *fieldNames[] = { + (char *) "ownerid" , + (char *) "row", + ATLAS_GET_STAT_NAME( NICK ), + ATLAS_GET_STAT_NAME( BEST_RACE_TIME ), + ATLAS_GET_STAT_NAME( CURRENT_WIN_STREAK ), + ATLAS_GET_STAT_NAME( CAREER_WINS ), + ATLAS_GET_STAT_NAME( CAREER_LOSSES ), + ATLAS_GET_STAT_NAME( TOTAL_COMPLETE_MATCHES ), + ATLAS_GET_STAT_NAME( LATITUDE ), + ATLAS_GET_STAT_NAME( LONGITUDE) + }; + + SAKEStartRequestResult startRequestResult; + + char filter[512]; + float myLat = gPlayerData.playerLocation.coordinate.latitude; + float myLong = gPlayerData.playerLocation.coordinate.longitude; + float offset = LOCATION_DEGREES_RADIUS; + + sprintf(filter, "BEST_RACE_TIME != 99999 and LATITUDE > %f and LATITUDE < %f and LONGITUDE > %f and LONGITUDE < %f", + myLat-offset, myLat+offset, myLong-offset, myLong+offset); + + input.mTableId = (char *) PLAYER_STATS_TABLE; + input.mFieldNames = fieldNames; + input.mNumFields = (sizeof(fieldNames) / sizeof(fieldNames[0])); + input.mFilter = filter; + input.mSort = (char *) "BEST_RACE_TIME asc"; + input.mOffset = 0; + input.mMaxRecords = 30; + + request = sakeSearchForRecords(appDelegate.sake, &input, SearchForLocationRecordsCallback, self); + if(!request) + { + startRequestResult = sakeGetStartRequestResult(appDelegate.sake); + NSLog(@"Failed to start location search request: %d\n", startRequestResult); + } +} + +- (void) sakeSearchForLocationRecordsRequestCallback:(SAKE) sake request:(SAKERequest) request result:(SAKERequestResult) result inputData:(void *) inputData outputData:(void *) outputData +{ + float lat; + float lng; + + if(result == SAKERequestResult_SUCCESS) { + SAKESearchForRecordsOutput* records = (SAKESearchForRecordsOutput*)outputData; + if ( records->mNumRecords > 0) { + // extract the info and put it into the buddy list and display it + if (gLeaderboardData != nil) + { + int lbtype = gLeaderboardType; + [appDelegate deleteLeaderboards]; + gLeaderboardType = lbtype; + } + + if (records->mNumRecords > 0) + { + gLeaderboardData = [[NSMutableArray arrayWithCapacity: records->mNumRecords] retain]; + + for (int i=0; imNumRecords; i++) + { + SAKEField * field = sakeGetFieldByName( "ownerid", records->mRecords[i], 10 ); + + // if this guy is me, skip it + if ((field != NULL) && ( (int)(gPlayerData.profileId) == field->mValue.mInt)) + { + /* // but extract my rank, just in case + field = sakeGetFieldByName( "row", records->mRecords[i], 10); + if (field != NULL) + { + gPlayerData.rank = field->mValue.mInt; + } + */ + continue; + } + + // create a new leaderboard object + LeaderboardObject * pData = [[LeaderboardObject alloc] init]; + + if (field != NULL) { + pData.profileId = field->mValue.mInt; + } else { + pData.profileId = 0; + } + field = sakeGetFieldByName( "row", records->mRecords[i], 10); + if (field != NULL) { + pData.rank = field->mValue.mInt; + } else { + pData.rank = -1; + } + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( NICK ), records->mRecords[i], 10 ); + if (field != NULL) { + pData.name = [NSString stringWithFormat: @"%s", (char*)( field->mValue.mAsciiString ) ]; + } else { + pData.name = nil; + } + NSLog(@"Found player[%d]: %s ", pData.rank, field->mValue.mAsciiString); + + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( BEST_RACE_TIME ), records->mRecords[i], 10 ); + if (field != NULL) { + pData.bestRaceTime = (gsi_u32)( field->mValue.mInt ); + } else { + pData.bestRaceTime = 0; + } + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( TOTAL_COMPLETE_MATCHES ), records->mRecords[i], 10 ); + if (field != NULL) { + pData.totalMatches = (gsi_u32)( field->mValue.mInt ); + } else { + pData.totalMatches = 0; + } + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( CAREER_WINS ), records->mRecords[i], 10 ); + if (field != NULL) { + pData.careerWins = (gsi_u32)( field->mValue.mInt ); + } else { + pData.careerWins = 0; + } + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( CAREER_LOSSES ), records->mRecords[i], 10 ); + if (field != NULL) { + pData.careerLosses = (gsi_u32)( field->mValue.mInt ); + } else { + pData.careerLosses = 0; + } + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( CURRENT_WIN_STREAK ), records->mRecords[i], 10 ); + if (field != NULL) { + pData.currentWinStreak = (gsi_u32)( field->mValue.mInt ); + } else { + pData.currentWinStreak = 0; + } + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( LATITUDE ), records->mRecords[i], 10 ); + if (field != NULL) { + lat = field->mValue.mFloat; + } else { + NSLog(@"- no lat "); + lat = 0; + } + field = sakeGetFieldByName( ATLAS_GET_STAT_NAME( LONGITUDE ), records->mRecords[i], 10 ); + if (field != NULL) { + lng = field->mValue.mFloat; + } else { + NSLog(@" - no long - "); + lng = 0; + } + NSLog(@" at lat=%f, long=%f\n", lat, lng); + + pData.playerLocation = [[CLLocation alloc] initWithLatitude: lat longitude: lng]; + [gLeaderboardData addObject: pData]; + } + + // redraw the map + [self viewDidLoad]; + } else { + if (alertView != nil) { + // remove the previous alert + [alertView dismissWithClickedButtonIndex: alertView.firstOtherButtonIndex animated: NO]; + [alertView release]; + alertView = nil; + } + alertView = [[UIAlertView alloc] initWithTitle: nil message: @"No nearby leaders" delegate: self cancelButtonTitle: @"OK" otherButtonTitles: nil]; + [alertView show]; + } + } + } else { + NSLog(@"Failed to search by location, error: %d\n", result); + } +} + +@end + +void SearchForLocationRecordsCallback( SAKE sake, SAKERequest request, SAKERequestResult result, void * inputData, void * outputData, void * userData ) +{ + [(LeadersByLocationController *)userData sakeSearchForLocationRecordsRequestCallback: sake request: request result: result inputData: inputData outputData: outputData ]; +} + diff --git a/Samples/TapRace/iphone/LeadersController.h b/Samples/TapRace/iphone/LeadersController.h new file mode 100644 index 00000000..2557af4a --- /dev/null +++ b/Samples/TapRace/iphone/LeadersController.h @@ -0,0 +1,21 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import + +@interface LeadersController : UIViewController +{ + UITableView* leadersList; // list of leader info + IBOutlet UILabel* tableContainer; // table GUI + NSMutableDictionary* buddyList; // preconstructed list of cell views + + UIAlertView* alertView; +} + +@property (nonatomic, retain) NSMutableDictionary* buddyList; + +- (IBAction)showInfo; + +- (void)updatePictureInTable; + +@end diff --git a/Samples/TapRace/iphone/LeadersController.mm b/Samples/TapRace/iphone/LeadersController.mm new file mode 100644 index 00000000..4fcbb71a --- /dev/null +++ b/Samples/TapRace/iphone/LeadersController.mm @@ -0,0 +1,223 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "LeadersController.h" +#import "AppDelegate.h" +#import "BuddyListCellView.h" + + +static NSString * kCellIdentifier1 = @"gobbletygoop"; + + +@implementation LeadersController + +@synthesize buddyList; + + +- (void)dealloc +{ + [tableContainer release]; + [leadersList release]; + [buddyList release]; + [super dealloc]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + // Return YES for supported orientations + return (interfaceOrientation == UIInterfaceOrientationPortrait); +} + +- (void)constructView +{ + // Create table. + CGRect rect = tableContainer.frame; + + // add the table view to it + leadersList = [[UITableView alloc] initWithFrame:tableContainer.frame style: UITableViewStylePlain]; + leadersList.backgroundColor = self.view.backgroundColor; + leadersList.delegate = self; + leadersList.dataSource = self; + leadersList.separatorStyle = UITableViewCellSeparatorStyleSingleLine; + leadersList.separatorColor = [UIColor blackColor]; + [self.view addSubview: leadersList]; + leadersList.frame = rect; + + if (gLeaderboardType == LEADERBOARD_BY_TIME) + { + self.title = [NSString stringWithString: @"Leaderboard"]; + } + else + { + self.title = [NSString stringWithString: @"Leaders by Buddies"]; + } + + if ([gLeaderboardData count] == 0) + { + if (gLeaderboardType == LEADERBOARD_BY_TIME) + { + alertView = [[UIAlertView alloc] initWithTitle: nil message: @"No leaders!" delegate: self cancelButtonTitle: @"OK" otherButtonTitles: nil]; + } + else + { + alertView = [[UIAlertView alloc] initWithTitle: nil message: @"No buddies have played!" delegate: self cancelButtonTitle: @"OK" otherButtonTitles: nil]; + } + + [alertView show]; + } +} + +- (void)alertView: (UIAlertView*)alert didDismissWithButtonIndex: (NSInteger)buttonIndex +{ + // same as exit + [self.navigationController popViewControllerAnimated: YES]; +} + +// Set each thumbnail that is blocked to default picture +-(void)updatePictureInTable +{ + LeaderboardObject* leader = (LeaderboardObject*)[gLeaderboardData objectAtIndex: gLeaderboardProfileIndex]; + if( [appDelegate imageIsBlocked:leader.pictureFileId ] ) + { + // get default image + NSString* defaultImage = [[NSBundle mainBundle] pathForResource:@"singlePlayer" ofType:@"png"]; + UIImage* imageObj = [[UIImage alloc] initWithContentsOfFile:defaultImage]; + + // set cell picture to default & refresh + NSNumber * key = [[NSNumber alloc] initWithInt:gLeaderboardProfileIndex]; + BuddyListCellView * view = [buddyList objectForKey: key]; + [view setImage: imageObj]; + [view setNeedsDisplay]; + } +} + +- (void)viewWillAppear: (BOOL)animated +{ + if (leadersList == nil) + { + [self constructView]; + } + else + { + [self updatePictureInTable ]; + } +} + + +// Display the About page +- (IBAction)showInfo +{ + [ appDelegate showAboutInfo: self ]; +} + + +- (UITableViewCell *)tableView:(UITableView *)tblView cellForRowAtIndexPath:(NSIndexPath *)indexPath +{ + UITableViewCell *cell = nil; + NSNumber * key = [NSNumber numberWithInteger: indexPath.row]; + unsigned int keyint = [key intValue]; + + if (keyint > [buddyList count]) + return nil; + + BuddyListCellView * view = [buddyList objectForKey: key]; + CGRect rect; + if (view == nil) + return nil; + + rect = view.cellView.frame; + cell = [tblView dequeueReusableCellWithIdentifier: kCellIdentifier1]; + if (cell == nil) + { + cell = [[[UITableViewCell alloc] initWithFrame: rect reuseIdentifier:kCellIdentifier1] autorelease]; + } + + // ??? need to change cell = initWithStyle:reuseIdentifier + //UITableViewCellAccessoryDisclosureIndicator ]; + + // modify the data for this cellview + UIView * uiview = nil; + for (uiview in cell.contentView.subviews) + break; // just get the first one (only one) + + if (uiview != nil) + { + // remove old one + [uiview removeFromSuperview]; // releases it and its contents + } + + // go ahead and add the current view + [cell.contentView addSubview: view.cellView]; // only add the 1st time it is visible + + return cell; +} + + +- (NSInteger)tableView:(UITableView *)tblView numberOfRowsInSection:(NSInteger)section +{ + if ((buddyList == nil) && (gLeaderboardData != nil) && ([gLeaderboardData count] > 0)) + { + // make the list before the table actually starts needing it + buddyList = [[NSMutableDictionary dictionaryWithCapacity: [gLeaderboardData count]] retain]; + } + return [gLeaderboardData count]; +} + +- (NSInteger)numberOfSectionsInTableView:(UITableView *)tblView +{ + return 1; +} + +- (CGFloat)tableView:(UITableView *)tblView heightForRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSNumber * key = [NSNumber numberWithInteger: indexPath.row]; + int keyint = [key intValue]; + LeaderboardObject* leader = (LeaderboardObject*)[gLeaderboardData objectAtIndex: keyint]; + + // now construct the cell for this entry in the table + BuddyListCellView * newcell = [[BuddyListCellView alloc] initCellView]; + + // fill in the fields + [newcell setName: leader.name]; + newcell.profileId = leader.profileId; + [newcell setIndex: keyint+1]; + UIImage * img = leader.thumbNail; + + // if no thumbnail, leave as default 'No Photo' image + if (img != nil && ![appDelegate imageIsBlocked: leader.pictureFileId]) + { + [newcell setImage: img]; + } + else + { + // kick off the process of getting the image + [newcell getImageFromNetwork]; + } + + [newcell setTime: leader.bestRaceTime]; + + // add to array + [buddyList setObject: newcell forKey: key]; + + CGRect rect = newcell.cellView.frame; + [newcell release]; + return rect.size.height+1; +} + +//Managing Accessory Views +- (UITableViewCellAccessoryType)tableView:(UITableView *)tblView accessoryTypeForRowWithIndexPath:(NSIndexPath *)indexPath +{ + return UITableViewCellAccessoryDisclosureIndicator; +} + +- (void)tableView:(UITableView *)tblView didSelectRowAtIndexPath:(NSIndexPath *)indexPath +{ + NSNumber * key = [NSNumber numberWithInteger: indexPath.row]; + int keyint = [key intValue]; + + [tblView deselectRowAtIndexPath:indexPath animated:YES]; + [appDelegate showUser: keyint]; +} + + +@end diff --git a/Samples/TapRace/iphone/LoginController.h b/Samples/TapRace/iphone/LoginController.h new file mode 100644 index 00000000..4c098ac6 --- /dev/null +++ b/Samples/TapRace/iphone/LoginController.h @@ -0,0 +1,46 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import +#import + +#import "GP/gp.h" + +@class CreateUserController; + +@interface LoginController : UIViewController +{ + IBOutlet UITextField* userNameField; + IBOutlet UIPickerView* userNamePicker; + IBOutlet UITextField* passwordField; + IBOutlet UISwitch* autoLoginSwitch; + IBOutlet UIActivityIndicatorView* activityIndicator; + IBOutlet UIButton* forgotPassword; + IBOutlet UIView* userNamePickerView; + IBOutlet UIButton* info; + + UIAlertView* alertView; + + NSTimer* loginTimer; + + NSArray* rememberedLogins; + + NSDictionary* rememberedPasswordsByLogin; + + GPConnection connection; +} + +- (IBAction)loginClicked: (id)sender; +- (IBAction)returnClicked: (id)sender; +- (IBAction)createClicked: (id)sender; +- (IBAction)forgotPasswordClicked: (id)sender; +- (IBAction)autoLoginChanged: (id)sender; + +- (void)userProfileCreated: (CreateUserController*)createUserController autoLogin: (BOOL) autoLoginValue; +- (void)stopLoginTimer; +- (void)reset; +- (void)delayedNetworkConnectionErrorMessage; +- (GPConnection *)getGPConnectionPtr; +- (BOOL)isNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags; + +@end diff --git a/Samples/TapRace/iphone/LoginController.mm b/Samples/TapRace/iphone/LoginController.mm new file mode 100644 index 00000000..c05920a9 --- /dev/null +++ b/Samples/TapRace/iphone/LoginController.mm @@ -0,0 +1,729 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "LoginController.h" +#import "AppDelegate.h" +#import "Utility.h" +#import "CreateUserController.h" +#import "ForgotPassword.h" +#import "InfoController.h" + +#import "common/gsAvailable.h" +#import "webservices/AuthService.h" + +static NSString* animationId = @"UserNamePickerAnimation"; + +//static NSString* loginFileName = @"login.plist"; +//static NSString* lastLoginKey = @"lastLogin"; +//static NSString* passwordsByLoginKey = @"passwordsByLogin"; + +static NSString* useridKey = @"useridKey"; +static NSString* passwordKey = @"passwordKey"; +static NSString* autologinKey = @"autologinKey"; + + + + +@interface LoginController(Private) + +- (void)doneClicked: (id)sender; +- (void)onLoginTimer: (NSTimer*)timer; +- (void)loginCallback: (GPResult)result profile: (GPProfile)profile uniquenick: (const char[])uniquenick; +- (void)loginCallback: (GHTTPResult)result certificate: (GSLoginCertificate*)certificate privateData: (GSLoginPrivateData*)privateData; +- (void)animationDidStop: (NSString*)animId finished: (BOOL)finished context: (id)context; +- (void)closeUserNamePicker; + +@end + +static void gpLoginCallback(GPConnection* connection, GPConnectResponseArg* arg, void* param); +static void gpUserKickedOutCallback(GPConnection* connection, void* arg, void* param); +static void wsLoginCallback(GHTTPResult httpResult, WSLoginResponse* response, void* userData); + +@implementation LoginController + +- (void)dealloc +{ + [userNameField release]; + [passwordField release]; + [autoLoginSwitch release]; + [activityIndicator release]; + [userNamePicker release]; + + [super dealloc]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation +{ + // Return YES for supported orientations + return interfaceOrientation == UIInterfaceOrientationPortrait; +} + +/*- (void)viewDidLoad +{ +/* if (![self isNetworkAvailableFlags:nil]) + { + MessageBox( @"A network connection is not available. Application will close.", @"OK" ); + exit(0); + } +*/ + +//static NSMutableString* errorMsg = @"error"; +// called after a delay to upload a new image from the image picker... +// gives the mainloop a chance to switch views and render the leaders menu again before the upload starts +- (void)delayedNetworkConnectionErrorMessage +{ + lostConnection = true; + //MessageBox( errMsg, @"Network Error" ); + MessageBox(@"Unable to establish connection. Please connect to a different WiFi network or switch to 3G.", @"Network Error"); +} + + +- (void)viewDidLoad +{ + // Basic initialization. + alertView = nil; + sdkInitialized = false; + lostConnection = false; + + [appDelegate initGame]; + + // load login info from user defaults + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + userNameField.text = [ defaults stringForKey: useridKey ]; + passwordField.text = [ defaults stringForKey: passwordKey ]; + autoLoginSwitch.on = [ defaults boolForKey: autologinKey ]; + + // Seed the random number generator (for generating cookie values for nat negotiation). + time_t currentTime; + time(¤tTime); + srand(currentTime); + + // Check Network connectivity + if (![self isNetworkAvailableFlags:nil]) + { + // display error message in a second to give the system time to display the login page + [self performSelector:@selector(delayedNetworkConnectionErrorMessage) withObject:nil afterDelay:1.0f]; + return; + } + + // Init gsCore, Competition & Sake SDKs + [appDelegate initSake ]; + + // Login if auto login is true + if( autoLoginSwitch.on ) + { + [ self loginClicked: nil ]; + } +} + + +/*- (void)viewWillAppear: (BOOL)animated +{ + if (![self isNetworkAvailableFlags:nil]) + { + MessageBox( @"A network connection is not available. Application will close.", @"OK" ); + exit(0); + } +} +*/ + +/* +- (void)viewDidLoad +{ + if (![self isNetworkAvailableFlags:nil]) { + MessageBox( @"A network connection is not available. Application will close.", @"No Network" ); + exit(0); + } + // load login info + ////////////////// + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + if ([paths count] > 0) { + NSString* documentsPath = (NSString*)[paths objectAtIndex: 0]; + NSString* loginFilePath = [documentsPath stringByAppendingPathComponent: loginFileName]; + NSDictionary* loginDictionary = [NSDictionary dictionaryWithContentsOfFile: loginFilePath]; + + if (loginDictionary != nil) { + NSString* lastLogin = (NSString*)[loginDictionary objectForKey: lastLoginKey]; + + if (lastLogin != nil) { + autoLoginSwitch.on = YES; + NSDictionary* passwordsByLogin = (NSDictionary*)[loginDictionary objectForKey: passwordsByLoginKey]; + + if (passwordsByLogin != nil) { + NSString* password = [passwordsByLogin objectForKey: lastLogin]; + + if (password != nil) { + userNameField.text = lastLogin; + passwordField.text = password; + } + } + } + } + } +} +*/ + +- (NSInteger)numberOfComponentsInPickerView: (UIPickerView*)pickerView +{ + return 1; +} + +- (NSInteger)pickerView: (UIPickerView*)pickerView numberOfRowsInComponent: (NSInteger)component +{ + return [rememberedLogins count] + 1; +} + +- (NSString*)pickerView: (UIPickerView*)pickerView titleForRow: (NSInteger)row forComponent: (NSInteger)component +{ + if (row < (int) ([rememberedLogins count]) ) + + { + return (NSString*)[rememberedLogins objectAtIndex: row]; + } + + return @""; +} + +- (void)pickerView: (UIPickerView*)pickerView didSelectRow: (NSInteger)row inComponent: (NSInteger)component +{ + if (row < (int) ([rememberedLogins count]) ) + { + NSString* login = (NSString*)[rememberedLogins objectAtIndex: row]; + userNameField.text = login; + passwordField.text = (NSString*)[rememberedPasswordsByLogin objectForKey: login]; + [autoLoginSwitch setOn: YES animated: YES]; + } + else { + userNameField.text = nil; + passwordField.text = nil; + [autoLoginSwitch setOn: NO animated: YES]; + } + +} + +// Perform a reachability query for the address 0.0.0.0. If that address is reachable without +// requiring a connection, a network interface is available. We'll have to do more work to +// determine which network interface is available. +- (BOOL)isNetworkAvailableFlags:(SCNetworkReachabilityFlags *)outFlags +{ + SCNetworkReachabilityRef defaultRouteReachability; + struct sockaddr_in zeroAddress; + bzero(&zeroAddress, sizeof(zeroAddress)); + zeroAddress.sin_len = sizeof(zeroAddress); + zeroAddress.sin_family = AF_INET; + + defaultRouteReachability = SCNetworkReachabilityCreateWithAddress(NULL, (struct sockaddr *)&zeroAddress); + + SCNetworkReachabilityFlags flags; + BOOL gotFlags = SCNetworkReachabilityGetFlags(defaultRouteReachability, &flags); + if (!gotFlags) + return NO; + + BOOL isReachable = flags & kSCNetworkReachabilityFlagsReachable; + + // This flag indicates that the specified nodename or address can + // be reached using the current network configuration, but a + // connection must first be established. + // + // If the flag is false, we don't have a connection. But because CFNetwork + // automatically attempts to bring up a WWAN connection, if the WWAN reachability + // flag is present, a connection is not required. + BOOL noConnectionRequired = !(flags & kSCNetworkReachabilityFlagsConnectionRequired); + if ((flags & kSCNetworkReachabilityFlagsIsWWAN)) { + noConnectionRequired = YES; + } + + isReachable = (isReachable && noConnectionRequired) ? YES : NO; + + // Callers of this method might want to use the reachability flags, so if an 'out' parameter + // was passed in, assign the reachability flags to it. + if (outFlags) { + *outFlags = flags; + } + + return isReachable; +} + +- (IBAction)loginClicked: (id)sender +{ + [appDelegate initGame ]; + [appDelegate initSake ]; + + // Check for no account info + if(([userNameField.text length] == 0) || ([passwordField.text length] == 0)) + { + MessageBox(@"Please fill in all the account information."); + return; + } + + // Initialize GP + GPResult gpresult = gpInitialize(&connection, SCRACE_PRODUCTID, WSLogin_NAMESPACE_SHARED_UNIQUE, GP_PARTNERID_GAMESPY); + if( gpresult == GP_MEMORY_ERROR ) + { + MessageBox(@"Could not initialize the login system", @"Memory Error"); + return; + } + else if( gpresult == GP_PARAMETER_ERROR) + { + MessageBox(@"Could not initialize the login system", @"Parameter Error"); + return; + } + else if( gpresult == GP_SERVER_ERROR) + { + MessageBox(@"Could not initialize the login system", @"Server Error"); + return; + } + else if( gpresult == GP_NETWORK_ERROR) + { + MessageBox(@"Could not initialize the login system", @"Network Error"); + return; + } + else if( gpresult != GP_NO_ERROR) + { + MessageBox(@"Error initializing the login system"); + return; + } + + // set callback to be notified if same user logs into another device + gpSetCallback(&connection, GP_ERROR , (GPCallback)gpUserKickedOutCallback, self); + + // Connect to the account specified + const char* userName = [userNameField.text cStringUsingEncoding: NSASCIIStringEncoding]; + const char* password = [passwordField.text cStringUsingEncoding: NSASCIIStringEncoding]; + + GPResult result = gpConnectUniqueNickA(&connection, userName, password, GP_FIREWALL, GP_NON_BLOCKING, (GPCallback)gpLoginCallback, self); + if (result != GP_NO_ERROR) + { + switch (result) + { + case GP_PARAMETER_ERROR: + MessageBox(@"Please enter a valid user name and password."); + return; + + default: + MessageBox(@"Login failed. Try again when network connection is up", @"Connection Error"); + return; + } + } + + alertView = [[UIAlertView alloc] initWithTitle: nil message: @"Logging in..." delegate: self cancelButtonTitle: @"Cancel" otherButtonTitles: nil]; + [alertView show]; + loginTimer = [NSTimer scheduledTimerWithTimeInterval: 0.050 target: self selector: @selector(onLoginTimer:) userInfo: nil repeats: YES]; + [activityIndicator startAnimating]; +} + +- (IBAction)returnClicked: (id)sender +{ + UITextField* textFields[] = { userNameField, passwordField }; + size_t textFieldCount = sizeof(textFields) / sizeof(textFields[0]); + size_t textFieldIndex = 0; + + // Find the text field that generated the event. + while (textFields[textFieldIndex] != sender) + textFieldIndex++; + + // Cycle focus to the next text field. + if (++textFieldIndex < textFieldCount) { + [textFields[textFieldIndex] becomeFirstResponder]; + } + else { + // [(UIResponder*)sender resignFirstResponder]; + } +} + + +- (IBAction)createClicked: (id)sender +{ + CreateUserController* createUserController = [[CreateUserController alloc] initWithNibName: @"createuser" bundle: nil]; + [self.navigationController pushViewController: createUserController animated: YES]; + createUserController.loginController = self; + createUserController.autoLoginSwitch.on = autoLoginSwitch.on; // use same setting as on login +} + +- (IBAction)forgotPasswordClicked: (id)sender +{ + ForgotPassword* forgotPasswordController = [[ForgotPassword alloc] initWithNibName: @"forgotPassword" bundle: nil]; + [self.navigationController pushViewController: forgotPasswordController animated: YES]; +} + +- (IBAction)autoLoginChanged: (id)sender +{ + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [ defaults setBool: autoLoginSwitch.on forKey: autologinKey ]; +} + +/* +- (IBAction)autoLoginChanged: (id)sender +{ + if (!autoLoginSwitch.on) { + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + + if ([paths count] > 0) { + NSString* documentsPath = (NSString*)[paths objectAtIndex: 0]; + NSString* loginFilePath = [documentsPath stringByAppendingPathComponent: loginFileName]; + NSMutableDictionary* loginDictionary = [NSMutableDictionary dictionaryWithContentsOfFile: loginFilePath]; + + if (loginDictionary != nil) { + [loginDictionary removeObjectForKey: lastLoginKey]; + + NSMutableDictionary* passwordsByLogin = [NSMutableDictionary dictionaryWithDictionary: (NSDictionary*)[loginDictionary objectForKey: passwordsByLoginKey]]; + + if (passwordsByLogin != nil) { + [passwordsByLogin removeObjectForKey: userNameField.text]; + [loginDictionary setObject: passwordsByLogin forKey: passwordsByLoginKey]; + } + + [loginDictionary writeToFile: loginFilePath atomically: YES]; + } + } + } +} +*/ +/*- (IBAction)autoLoginChanged: (id)sender +{ + if (!rememberMeSwitch.on) { + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + + if ([paths count] > 0) { + NSString* documentsPath = (NSString*)[paths objectAtIndex: 0]; + NSString* loginFilePath = [documentsPath stringByAppendingPathComponent: loginFileName]; + NSMutableDictionary* loginDictionary = [NSMutableDictionary dictionaryWithContentsOfFile: loginFilePath]; + + if (loginDictionary != nil) { + [loginDictionary removeObjectForKey: lastLoginKey]; + + NSMutableDictionary* passwordsByLogin = [NSMutableDictionary dictionaryWithDictionary: (NSDictionary*)[loginDictionary objectForKey: passwordsByLoginKey]]; + + if (passwordsByLogin != nil) { + [passwordsByLogin removeObjectForKey: userNameField.text]; + [loginDictionary setObject: passwordsByLogin forKey: passwordsByLoginKey]; + } + + [loginDictionary writeToFile: loginFilePath atomically: YES]; + } + } + } +} +*/ + + +- (void)userProfileCreated: (CreateUserController*)createUserController autoLogin: (BOOL) autoLoginValue +{ + // if the autoLogin flag is true, turn on the switch first + autoLoginSwitch.on = autoLoginValue; + + // These objects disappear as soon as we pop the view controller. + // Popping the view conroller will trigger the viewWillAppear message, which calls reset, which + // destroys the login controller's connection object. Since we want to preserve the connection + // object created by the createUserController, assign it to a temporary variable rather than to + // self->connection. + GPConnection tempConnection = createUserController.connection; + userNameField.text = createUserController.uniquenick; + passwordField.text = createUserController.password; + + // This line destroys createUserController and triggers the viewWillAppear message. + [self.navigationController popToViewController: self animated: YES]; + + // By this point, viewWillAppear has been called, so it should now be safe to assign the connection + // object. + connection = tempConnection; + + // Continue the login process as usual. + loginTimer = [NSTimer scheduledTimerWithTimeInterval: 0.050 target: self selector: @selector(onLoginTimer:) userInfo: nil repeats: YES]; + [self loginCallback: GP_NO_ERROR profile: createUserController.profile uniquenick: [userNameField.text cStringUsingEncoding: NSASCIIStringEncoding]]; +} + +- (void)reset +{ + gpDisconnect(&connection); + gpDestroy(&connection); + + // Basic initialization. + alertView = nil; + sdkInitialized = false; + lostConnection = false; + + // clean up gPlayerData ... PlayerData should be promoted to a full object! + [gPlayerData.uniquenick release]; + gPlayerData.uniquenick = nil; + [gPlayerData.thumbNail release]; + gPlayerData.thumbNail = nil; + [gPlayerData.fullsizeImage release]; + gPlayerData.fullsizeImage = nil; + [gPlayerData.playerLocation release]; + gPlayerData.playerLocation = nil; + [gPlayerData.playerDataPath release]; + gPlayerData.playerDataPath = nil; + [gPlayerData.playerStatsData release]; + gPlayerData.playerStatsData = nil; + + // clean up gOpponentData + [gOpponentData.uniquenick release]; + gOpponentData.uniquenick = nil; + [gOpponentData.thumbNail release]; + gOpponentData.thumbNail = nil; + [gOpponentData.fullsizeImage release]; + gOpponentData.fullsizeImage = nil; + [gOpponentData.playerLocation release]; + gOpponentData.playerLocation = nil; + [gOpponentData.playerDataPath release]; + gOpponentData.playerDataPath = nil; + [gOpponentData.playerStatsData release]; + gOpponentData.playerStatsData = nil; + + // clean up leaderboards + [appDelegate deleteLeaderboards]; +} + +- (GPConnection *)getGPConnectionPtr +{ + return &connection; +} + + +- (void)stopLoginTimer +{ + if (loginTimer != nil) + { + [loginTimer invalidate]; + loginTimer = nil; + } +} + +@end + + +@implementation LoginController(Private) + +- (void)alertView: (UIAlertView*)alert didDismissWithButtonIndex: (NSInteger)buttonIndex +{ + if (buttonIndex == alertView.cancelButtonIndex) + { + [loginTimer invalidate]; + gpDisconnect(&connection); + gpDestroy(&connection); + + [activityIndicator stopAnimating]; + } +} + +- (BOOL)textFieldShouldBeginEditing: (UITextField*)textField +{ +/* if (textField == userNameField) { + if (userNamePickerView.hidden) { + NSArray* paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + + if ([paths count] > 0) { + NSString* documentsPath = (NSString*)[paths objectAtIndex: 0]; + NSString* loginFilePath = [documentsPath stringByAppendingPathComponent: loginFileName]; + NSDictionary* loginDictionary = [NSDictionary dictionaryWithContentsOfFile: loginFilePath]; + + if (loginDictionary != nil) { + // Released when the user disimisses the picker view. + rememberedPasswordsByLogin = [(NSDictionary*)[loginDictionary objectForKey: passwordsByLoginKey] retain]; + + if (rememberedPasswordsByLogin != nil) { + // Released when the user disimisses the picker view. + rememberedLogins = [[rememberedPasswordsByLogin allKeys] retain]; + + if ([rememberedLogins count] > 0) { + [userNamePicker reloadAllComponents]; + NSUInteger currentIndex = [rememberedLogins indexOfObject: userNameField.text]; + + if (currentIndex == NSNotFound) { + currentIndex = [rememberedLogins count]; + } + + [userNamePicker selectRow: currentIndex inComponent: 0 animated: NO]; + + [passwordField resignFirstResponder]; + self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemDone target: self action: @selector(closeUserNamePicker)] autorelease]; + userNamePickerView.alpha = 0.0f; + userNamePickerView.hidden = NO; + + [UIView beginAnimations: animationId context: nil]; + userNamePickerView.alpha = 1.0f; + [UIView commitAnimations]; + return NO; + } + } + } + } + } + } +*/ + return YES; +} + +- (void)textFieldDidBeginEditing: (UITextField*)textField +{ + //self.navigationItem.rightBarButtonItem = [[[UIBarButtonItem alloc] initWithBarButtonSystemItem: UIBarButtonSystemItemDone target: self action: @selector(doneClicked:)] autorelease]; +} + +- (void)textFieldDidEndEditing:(UITextField *)textField +{ + //self.navigationItem.rightBarButtonItem = nil; +} + +- (void)doneClicked: (id)sender +{ + UITextField* textFields[] = { userNameField, passwordField }; + size_t textFieldCount = sizeof(textFields) / sizeof(textFields[0]); + + for (size_t n = 0; n < textFieldCount; n++) { + [textFields[n] resignFirstResponder]; + } +} + +- (void)animationDidStop: (NSString*)animId finished: (BOOL)finished context: (id)context +{ + if ([userNamePicker selectedRowInComponent: 0] == [userNamePicker numberOfRowsInComponent: 0] - 1) { + [userNameField becomeFirstResponder]; + } + + userNamePickerView.hidden = YES; +} + +- (void)closeUserNamePicker +{ + [rememberedLogins release]; + [rememberedPasswordsByLogin release]; + + //self.navigationItem.rightBarButtonItem = nil; + + [UIView beginAnimations: animationId context: nil]; + [UIView setAnimationDelegate: self]; + [UIView setAnimationDidStopSelector: @selector(animationDidStop:finished:context:)]; + userNamePickerView.alpha = 0.0f; + [UIView commitAnimations]; +} + +- (void)onLoginTimer: (NSTimer*)timer +{ + //GPEnum connected; + + // We are still using core! + // The logic here is to think when the core is valid + // the core is valid + // 1) GSCore_IN_USE + // 2) GSCore_SHUTTING_DOWN + + // We may not have started using core also + if (gsCoreIsShutdown() != GSCore_SHUTDOWN_COMPLETE) + gsCoreThink(0); + + if (connection) + gpProcess(&connection); +} + +- (void)loginCallback: (GPResult)result profile: (GPProfile)profile uniquenick: (const char[])uniquenick +{ + [alertView dismissWithClickedButtonIndex: alertView.firstOtherButtonIndex animated: YES]; + + if (result != GP_NO_ERROR) { + [loginTimer invalidate]; + loginTimer = nil; + [activityIndicator stopAnimating]; + char errorString[GP_ERROR_STRING_LEN]; + gpGetErrorStringA(&connection, errorString); + MessageBox([NSString stringWithCString: errorString encoding: NSASCIIStringEncoding], @"Login Failed"); + return; + } + + // Save the login info and autosave flag for next time + NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults]; + [ defaults setObject: userNameField.text forKey: useridKey ]; + [ defaults setObject: passwordField.text forKey: passwordKey ]; + [ defaults setBool: autoLoginSwitch.on forKey: autologinKey ]; + + // Store the profile ID for the player + ////////////////////////////////////// + gPlayerData.uniquenick = [[NSString alloc] initWithCString: uniquenick encoding: NSASCIIStringEncoding]; + gPlayerData.profileId = profile; + gpGetLoginTicket(&connection, gPlayerData.loginTicket); + + NSLog(@"login ticket[%s]", gPlayerData.loginTicket); + + const char* password = [passwordField.text cStringUsingEncoding: NSASCIIStringEncoding]; + gsi_u32 wsLoginResult = wsLoginUnique(WSLogin_PARTNERCODE_GAMESPY, WSLogin_NAMESPACE_SHARED_UNIQUE, uniquenick, password, NULL, wsLoginCallback, self); + + if (wsLoginResult != WSLogin_Success) + { + switch (wsLoginResult) + { + // Just output same error message for all login error type + default: + MessageBox(@"Failed to login to authentication service.", @"Login Failed"); + break; + } + + [loginTimer invalidate]; + loginTimer = nil; + [activityIndicator stopAnimating]; + gpDisconnect(&connection); + gpDestroy(&connection); + } +} + +- (void)loginCallback: (GHTTPResult)result certificate: (GSLoginCertificate*)certificate privateData: (GSLoginPrivateData*)privateData +{ + + if (result != GHTTPSuccess) + { + [activityIndicator stopAnimating]; + switch (result) + { + // Just output same error message for all login error type + default: + MessageBox(@"Failed to login to authentication service.", @"Login Failed"); + break; + } + + [loginTimer invalidate]; + loginTimer = nil; + + gpDisconnect(&connection); + gpDestroy(&connection); + + return; + } + + // Store the Login Certificate & Private Data for later use w/ Competition + ////////////////////////////////////////////////////////////////////////// + memcpy(&gPlayerData.certificate, certificate, sizeof(GSLoginCertificate)); + memcpy(&gPlayerData.privateData, privateData, sizeof(GSLoginPrivateData)); + + [activityIndicator stopAnimating]; + [appDelegate loggedIn]; +} + +@end + + +void gpUserKickedOutCallback( GPConnection* connection, void* arg, void* param ) +{ + GPErrorCode err = ((GPErrorArg*)arg)->errorCode; + if( err == GP_FORCED_DISCONNECT || err == GP_CONNECTION_CLOSED ) + { + // LoginController* loginController = (LoginController*)[appDelegate findExistingControllerOfType: [LoginController class]]; + // [loginController stopLoginTimer]; + + [appDelegate kickedOut]; + NSLog(@"User logged into other device.\n"); + MessageBox( @"You have been disconnected because another player has logged in with your ID.", @"Logged Out" ); + [appDelegate logout]; + } +} + + +void gpLoginCallback(GPConnection* connection, GPConnectResponseArg* arg, void* param) +{ + LoginController* loginController = (LoginController*)param; + [loginController loginCallback: arg->result profile: arg->profile uniquenick: arg->uniquenick]; +} + +void wsLoginCallback(GHTTPResult httpResult, WSLoginResponse* response, void* userData) +{ + LoginController* loginController = (LoginController*)userData; + [loginController loginCallback: httpResult certificate: &response->mCertificate privateData: &response->mPrivateData]; +} diff --git a/Samples/TapRace/iphone/MatchmakingController.h b/Samples/TapRace/iphone/MatchmakingController.h new file mode 100644 index 00000000..45147cb1 --- /dev/null +++ b/Samples/TapRace/iphone/MatchmakingController.h @@ -0,0 +1,37 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import +#import + +@interface MatchmakingController : UIViewController +{ + IBOutlet UIPickerView *gamePicker; + IBOutlet UIButton *joinButton; + IBOutlet UIButton *hostButton; + IBOutlet UIButton *RefreshButton; + IBOutlet UILabel *shakeLabel; + IBOutlet UILabel *listCountLabel; + + UIAccelerationValue myAccelerometer[3]; // high pass shake detection + + std::vector servers; +} + +- (IBAction)joinButtonClicked: (id)sender; +- (IBAction)hostButtonClicked: (id)sender; +- (IBAction)refreshButtonClicked: (id)sender; + +//- (void)hostCountdown: (int)count; +- (void)hostingCanceled; +- (void)enableShake: (bool) enable; + +- (void)reset; + +- (void)addServer: (NSString*)name address: (unsigned int)address port: (unsigned short)port useNatNeg: (bool)useNatNeg; +- (void)updateServer: (NSString*)name address: (unsigned int)address port: (unsigned short)port; +- (void)removeServer: (unsigned int)address port: (unsigned short)port; + +- (void)updatePicker; + +@end diff --git a/Samples/TapRace/iphone/MatchmakingController.mm b/Samples/TapRace/iphone/MatchmakingController.mm new file mode 100644 index 00000000..13648bb1 --- /dev/null +++ b/Samples/TapRace/iphone/MatchmakingController.mm @@ -0,0 +1,522 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +// Matchmaking is for selecting/looking for a server (aka player) to play a game with + +#import "MatchmakingController.h" +#import "LoginController.h" +#import "AppDelegate.h" +#import "Utility.h" + +#import "qr2/qr2.h" + +// Values for detecting shake +#define kAccelerometerFrequency 25 //Hz +#define kFilteringFactor 0.1 +#define kMinShakeInterval 0.5 +#define kShakeAccelerationThreshold 3.0 + +// Structure to hold server connection info +struct ServerData +{ + NSString* name; // Server name + unsigned int address; // IP address + unsigned short port; // port to connect on + bool useNatNeg; // true if IP is not local and NAT negotiation is required +}; + + +// Server Browsing Callback +static void SBCallback(ServerBrowser sb, SBCallbackReason reason, SBServer server, void* instance); + +static bool sbeError = false; + + +@interface MatchmakingController(Private) + +@end + + +@implementation MatchmakingController + +static gsi_bool sbUpdateFinished = gsi_false; // true when query for list of servers is complete + +// Release objects in page & server list +// ===================================== +- (void)dealloc +{ + for (std::vector::iterator i = servers.begin(); i != servers.end(); ++i) { + [i->name release]; + } + + [gamePicker release]; + [joinButton release]; + [hostButton release]; + [shakeLabel release]; + + [super dealloc]; +} + + +// Do not support auto-rotate +// ===================================== +- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation +{ + // Support portrait orientation only + return interfaceOrientation == UIInterfaceOrientationPortrait; +} + + +// When page will appear +// - setup GUI logic +// - start query for list of servers +// ===================================== +- (void)viewWillAppear: (BOOL)animated +{ + // tell host to quit game if I just came from gameController + [appDelegate stopHostingServer]; + + // setup GUI + self.title = @"MatchMaking"; + joinButton.enabled = NO; + //[RefreshButton setTitle: @"Refresh" forState: UIControlStateNormal ]; + //[RefreshButton setTitle: @"Refreshing" forState: UIControlStateHighlighted ]; + + // Query server list + ServerBrowser serverBrowser = ServerBrowserNewA(SCRACE_GAMENAME, SCRACE_GAMENAME, SCRACE_SECRETKEY, 0, MAX_SERVERS, QVERSION_QR2, SBFalse, SBCallback, self); + appDelegate.serverBrowser = serverBrowser; + + unsigned char basicFields[] = { HOSTNAME_KEY, HOSTPORT_KEY }; + size_t basicFieldCount = sizeof(basicFields) / sizeof(basicFields[0]); + sbUpdateFinished = gsi_false; + + // Get available servers + const char freeServersFilter[] = {"numplayers < maxplayers" }; + sbeError = false; + SBError result = ServerBrowserLimitUpdate(serverBrowser, SBTrue, SBTrue, basicFields, basicFieldCount, freeServersFilter, MAX_SERVERS); + + if (result != sbe_noerror) + { + sbeError = true; + + // MessageBox(@"Error communicating with GameSpy master server."); + // ServerBrowserFree(serverBrowser); + return; + } + + //Configure and enable the accelerometer + [UIAccelerometer sharedAccelerometer].updateInterval = (1.0 / kAccelerometerFrequency); + [UIAccelerometer sharedAccelerometer].delegate = self; +} + +- (void)viewDidAppear: (BOOL)animated +{ + if ( sbeError ) + { + //MessageBox(@"Error retrieving Games.", @"Connection Error"); + //ServerBrowserFree(appDelegate.serverBrowser); + return; + } + +} + + + +// When the page will disappear +// - release servers +// - stop hosting timer if any +// - stop check for shake +// ===================================== +- (void)viewWillDisappear: (BOOL)animated +{ + // Clear server list in GUI + for (std::vector::iterator i = servers.begin(); i != servers.end(); ++i) + { + [i->name release]; + } + servers.clear(); + + // Stop hosting if it was + [appDelegate stopHostingCountdownTimer]; + + // Clear server list in structure + ServerBrowser serverBrowser = appDelegate.serverBrowser; + if (serverBrowser != NULL) + { + ServerBrowserFree(serverBrowser); + appDelegate.serverBrowser = NULL; + } + + // Stop checking for shake when close matchmaking page + UIAccelerometer* accelerometer = [UIAccelerometer sharedAccelerometer]; + if (accelerometer.delegate == self) + accelerometer.delegate = nil; +} + + +// When Join button is pressed +// - update GUI logic +// - update server list to see is selected server is still available +// - if it is, initiate join to remote server +// ===================================== +- (IBAction)joinButtonClicked: (id)sender +{ + // disable join & accelarometer + [self enableShake:NO]; + joinButton.enabled = NO; + + // get selected player name from list + NSInteger index = [gamePicker selectedRowInComponent: 0]; + NSString *selectedServerName = servers.at(index).name; ; + NSString *serverName = [[NSString alloc] initWithString: selectedServerName ] ; + + // renew server list + [self refreshButtonClicked: sender ]; + + // wait for server list to update + while ((ServerBrowserThink(appDelegate.serverBrowser) == sbe_noerror) && (sbUpdateFinished == gsi_false)) + msleep(10); + + index = -1; + BOOL foundServer = NO; + + // get selected server name from renewed list & highlight selected server + for( std::vector::iterator i = servers.begin(); i != servers.end(); ++i) + { + index++; + if( [serverName compare: i->name] == NSOrderedSame ) + { + foundServer = YES; + [gamePicker selectRow: index inComponent: 0 animated: YES ]; + break; + } + } + + // if server name is not found, popup error + if( foundServer == NO ) + { + MessageBox(@"is not hosting anymore. Select another player.", serverName); + [self enableShake:YES]; + joinButton.enabled = NO; + return; + } + + // else join match + + // get selected player from list + index = [gamePicker selectedRowInComponent: 0]; + ServerData* data = &servers[index]; + + // connect to host player + // if not connected or join canceled, send NN cancel to host server + if( [appDelegate joinGame: data->address port: data->port useNatNeg: data->useNatNeg] == false ) + { + [self enableShake:YES]; + joinButton.enabled = NO; + //SBSendMessageToServer(sbServer, data->address, data->port, "CANCEL_CONNECTION", <#int len#>); + } +} + + +// Called when Host button clicked +// - setup GUI +// - initiate registration of host with GameSpy server +// ===================================== +- (IBAction)hostButtonClicked: (id)sender +{ + // turn off the join button and accelerometer + joinButton.enabled = NO; + [ self enableShake: NO]; + + if ( ! [appDelegate hostGame] ) + { + [self hostingCanceled ]; + } +} + + +// Called when user cancels Hosting or timeout +// - release host countdwon timer +// - setup GUI logic +// ===================================== +- (void)hostingCanceled +{ + [appDelegate stopHostingCountdownTimer]; + + if( servers.size() > 0 ) + joinButton.enabled = YES; + + [ self enableShake: YES]; +} + + +// Enables/disables shake detection +// ===================================== +- (void)enableShake: (bool) enable +{ + if( enable ) + { + shakeLabel.hidden = NO; + [UIAccelerometer sharedAccelerometer].delegate = self; + } + else + { + shakeLabel.hidden = YES; + + UIAccelerometer* accelerometer = [UIAccelerometer sharedAccelerometer]; + if (accelerometer.delegate == self) + accelerometer.delegate = nil; + } +} + + +// Called when user cancels Hosting or timeout +// - reload server list in GUI +// - setup GUI logic +// ===================================== +- (void)reset +{ + [appDelegate stopHostingCountdownTimer]; + + // get new server list + [gamePicker reloadAllComponents]; + + [ self enableShake: YES]; + + // enable join if there are servers + joinButton.enabled = ( servers.size() > 0 ); +} + +// +// ===================================== +- (void)addServer: (NSString*)name address: (unsigned int)address port: (unsigned short)port useNatNeg: (bool)useNatNeg +{ + servers.push_back((ServerData){ [name copy], address, port, useNatNeg }); +} + + +// ===================================== +- (void)updateServer: (NSString*)name address: (unsigned int)address port: (unsigned short)port +{ + for (std::vector::iterator i = servers.begin(); i != servers.end(); ++i) { + if ((i->address == address) && (i->port == port)) { + [i->name release]; + i->name = [name copy]; + break; + } + } +} + + +// ===================================== +- (void)removeServer: (unsigned int)address port: (unsigned short)port +{ + for (std::vector::iterator i = servers.begin(); i != servers.end(); ++i) { + if ((i->address == address) && (i->port == port)) { + [i->name release]; + servers.erase(i); + break; + } + } +} + + +// ===================================== +- (void)updatePicker +{ + [gamePicker reloadAllComponents]; + + listCountLabel.text = [NSString stringWithFormat: @"Games available: %d", servers.size() ]; + + [self enableShake:YES]; + + // enable join & shake if there are servers + if( servers.size() > 0 ) + joinButton.enabled = YES; +} + + +// Called when the accelerometer detects motion; if shaken with enough force, makes a random selection from the choices. +// ===================================== +- (void) accelerometer:(UIAccelerometer*)accelerometer didAccelerate:(UIAcceleration*)acceleration +{ + UIAccelerationValue length, x, y, z; + + //Use a basic high-pass filter to remove the influence of the gravity + myAccelerometer[0] = acceleration.x * kFilteringFactor + myAccelerometer[0] * (1.0 - kFilteringFactor); + myAccelerometer[1] = acceleration.y * kFilteringFactor + myAccelerometer[1] * (1.0 - kFilteringFactor); + myAccelerometer[2] = acceleration.z * kFilteringFactor + myAccelerometer[2] * (1.0 - kFilteringFactor); + + // Compute values for the three axes of the acceleromater + x = acceleration.x - myAccelerometer[0]; + y = acceleration.y - myAccelerometer[0]; + z = acceleration.z - myAccelerometer[0]; + + //Compute the intensity of the current acceleration + length = sqrt(x * x + y * y + z * z); + + // If above a given threshold, pick a server at random and join + if( length >= kShakeAccelerationThreshold ) + { + //NSInteger n = [gamePicker numberOfRowsInComponent: 0]; + + // Update server list if none in it + if( servers.size() < 1 ) + { + // renew server list + [self refreshButtonClicked: self ]; + + // wait for server list to update + while ((ServerBrowserThink(appDelegate.serverBrowser) == sbe_noerror) && (sbUpdateFinished == gsi_false)) + msleep(10); + } + + // Host if no servers available + if( servers.size() < 1 ) + { + [self hostButtonClicked: self ]; + } + else + { + // select random player to join + int index = (rand() & INT_MAX) % servers.size(); + ServerData* data = &servers[index]; + + // turn off the join button and accelerometer + joinButton.enabled = NO; + [ self enableShake: NO]; + + // join + [appDelegate joinGame: data->address port: data->port useNatNeg: data->useNatNeg]; + } + + } +} + + +// ===================================== +- (IBAction)refreshButtonClicked: (id)sender +{ + joinButton.enabled = NO; + [self viewWillDisappear: NO]; // use the shutdown function to kill servers and picker list + [self viewWillAppear: NO]; // and pretend we're starting up again + [self updatePicker]; + +} + + +/* ===================================== +- (void)hostCountdown: (int) count +{ + if (hosting) + { + alertView.message = @"qwe qwe"; + } + + //[gameController countdown: countdown]; + +} +*/ +@end + + +@implementation MatchmakingController(Private) + +- (NSString*)pickerView: (UIPickerView*)pickerView titleForRow: (NSInteger)row forComponent: (NSInteger)component +{ + return servers[row].name; +} + +- (void)pickerView: (UIPickerView*)pickerView didSelectRow: (NSInteger)row inComponent: (NSInteger)component +{ +} + +- (NSInteger)numberOfComponentsInPickerView: (UIPickerView*)pickerView +{ + return 1; +} + +- (NSInteger)pickerView: (UIPickerView*)pickerView numberOfRowsInComponent: (NSInteger)component +{ + return servers.size(); +} + +@end + + +void SBCallback(ServerBrowser sb, SBCallbackReason reason, SBServer server, void* instance) +{ + MatchmakingController* controller = (MatchmakingController*)instance; + + switch (reason) { + case sbc_serveradded: + case sbc_serverupdated: + case sbc_serverupdatefailed: + case sbc_serverdeleted: + { + NSLog(@"SBCallback added/updated/deleted"); + unsigned int address = SBServerGetPublicInetAddress(server); + unsigned short port = SBServerGetPublicQueryPort(server); + bool useNatNeg = false; + + // Server is behind a NAT device; check and see if it's local. + if (SBServerHasPrivateAddress(server) && (address == ServerBrowserGetMyPublicIPAddr(sb))) { + // Use the private address. + address = SBServerGetPrivateInetAddress(server); + port = SBServerGetPrivateQueryPort(server); + } + else { + // We'll have to use NAT negotiation to connect. + useNatNeg = true; + } + + + NSString* name = [NSString stringWithCString: SBServerGetStringValueA(server, qr2_registered_key_list[HOSTNAME_KEY], "Unnamed Game") encoding: NSASCIIStringEncoding]; + + switch (reason) { + case sbc_serveradded: + if( [name compare: gPlayerData.uniquenick ] != NSOrderedSame ) + { + [controller addServer: name address: address port: port useNatNeg: useNatNeg]; + NSLog(@"SB adding"); + } + break; + + case sbc_serverupdated: + [controller updateServer: name address: address port: port]; + NSLog(@"SB updating"); + break; + + case sbc_serverupdatefailed: + case sbc_serverdeleted: + [controller removeServer: address port: port]; + NSLog(@"SB deleting"); + break; + + case sbc_updatecomplete: + case sbc_queryerror: + case sbc_serverchallengereceived: + NSLog(@"SB nothing to do code=%d", reason); + break; + } + break; + } + + case sbc_updatecomplete: + NSLog(@"SBCallback sbc_updatecomplete"); + sbUpdateFinished = gsi_true; + [controller updatePicker]; + break; + + case sbc_queryerror: + NSLog(@"SBCallback sbc_queryerror"); + sbUpdateFinished = gsi_true; + MessageBox([NSString stringWithCString: ServerBrowserListQueryErrorA(sb) encoding: NSASCIIStringEncoding]); + break; + + case sbc_serverchallengereceived: + NSLog(@"SBCallback sbc_serverchallengereceived"); + break; + } +} + diff --git a/Samples/TapRace/iphone/MenuController.h b/Samples/TapRace/iphone/MenuController.h new file mode 100644 index 00000000..f3e079e5 --- /dev/null +++ b/Samples/TapRace/iphone/MenuController.h @@ -0,0 +1,37 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import +#import "MyCLController.h" +#import "FlipsideViewController.h" + +@interface MenuController : UIViewController +{ + IBOutlet UIButton* singlePlayerButton; + IBOutlet UIButton* multiPlayerButton; + IBOutlet UIButton* leaderboardsButton; + IBOutlet UILabel* leadersLabel; + IBOutlet UILabel* leadersPositionLabel; + IBOutlet UILabel* leadersTimeLabel; + + NSTimer* displayLeaderTimer; + + BOOL isCurrentlyUpdating; + UIAlertView* alertView; +} + +@property (nonatomic, retain) UILabel* leadersLabel; +@property (nonatomic, retain) UILabel* leadersPositionLabel; +@property (nonatomic, retain) UILabel* leadersTimeLabel; + +- (IBAction)onSinglePlayerClicked: (id)sender; +- (IBAction)onMultiPlayerClicked: (id)sender; +- (IBAction)onLeaderboardsClicked: (id)sender; +//- (IBAction)showAbout: (id) sender; +- (IBAction)showInfo; + +- (void)reset; +-(void)noTop5Leaders; + + +@end diff --git a/Samples/TapRace/iphone/MenuController.mm b/Samples/TapRace/iphone/MenuController.mm new file mode 100644 index 00000000..d56effe2 --- /dev/null +++ b/Samples/TapRace/iphone/MenuController.mm @@ -0,0 +1,230 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "MenuController.h" +#import "AboutController.h" +#import "AppDelegate.h" +#import + +#define ROTATE_LEADER_TIME 3 // seconds + +@interface MenuController(Private) + +- (void)updateLeaderLabel: (NSTimer*)timer; + +@end + + + +@implementation MenuController + + +@synthesize leadersLabel; +@synthesize leadersPositionLabel; +@synthesize leadersTimeLabel; + +- (void)dealloc +{ + [MyCLController sharedInstance].delegate = nil; // we won't handle this any more + [singlePlayerButton release]; + [multiPlayerButton release]; + [leaderboardsButton release]; + [super dealloc]; +} + +- (BOOL)shouldAutorotateToInterfaceOrientation: (UIInterfaceOrientation)interfaceOrientation +{ + // Return YES for supported orientations + return interfaceOrientation == UIInterfaceOrientationPortrait; +} + + + +- (IBAction)onSinglePlayerClicked: (id)sender +{ + [appDelegate startSinglePlayerGame]; +} + +- (IBAction)onMultiPlayerClicked: (id)sender +{ + [appDelegate findGame]; +} + +- (IBAction)onLeaderboardsClicked: (id)sender +{ + [appDelegate showLeaderboards]; +} + +- (void)reset +{ + // Empty +} + +- (void)updatePosition: (BOOL)start +{ + if (!start) { + [[MyCLController sharedInstance].locationManager stopUpdatingLocation]; + isCurrentlyUpdating = NO; + } else { + [[MyCLController sharedInstance].locationManager startUpdatingLocation]; + isCurrentlyUpdating = YES; + } +} + + +- (void)viewWillDisappear: (BOOL)animated +{ + [self updatePosition: NO]; + + if( displayLeaderTimer != nil ) + { + [displayLeaderTimer invalidate]; + } +} + + +- (void)viewDidLoad +{ + isCurrentlyUpdating = NO; + + [MyCLController sharedInstance].delegate = self; + + // Check to see if the user has disabled location services + // In that case, we just print a message + NSString *modelType = [UIDevice currentDevice].model; + if( [modelType isEqualToString: (NSString *) @"iPhone" ] ) + { + isCurrentlyUpdating = YES; + + if ( ! [MyCLController sharedInstance].locationManager.locationServicesEnabled ) + { + alertView = [[UIAlertView alloc] initWithTitle: nil message: @"Location Services Disabled" delegate: self cancelButtonTitle: @"OK" otherButtonTitles: nil]; + [alertView show]; + [self updatePosition: NO]; + } + else + { + [self updatePosition: YES]; + } + } +} + +- (void)viewWillAppear: (BOOL) animated +{ + self.title = @"Menu"; + [appDelegate showTop5LeadersByTime]; + + displayLeaderTimer = [NSTimer scheduledTimerWithTimeInterval: ROTATE_LEADER_TIME target: self selector: @selector(updateLeaderLabel:) userInfo: nil repeats: YES]; +} + + +-(void)noTop5Leaders +{ + leadersLabel.text = @"No leaders yet!"; + [displayLeaderTimer invalidate]; +} + + +- (void)alertView: (UIAlertView*)alert didDismissWithButtonIndex: (NSInteger)buttonIndex +{ +} + + + - (void)flipsideViewControllerDidFinish:(FlipsideViewController *)controller + { + [self dismissModalViewControllerAnimated:YES]; + } + + + - (IBAction)showInfo + { + [ appDelegate showAboutInfo: self ]; + } + + + +#pragma mark ---- delegate methods for the MyCLController class ---- +-(void)newLocationUpdateLat:(float)lat Long: (float)lng +{ + NSLog(@"updated player location to %.5f %.5f", lat, lng); + isCurrentlyUpdating = NO; + if (gPlayerData.playerLocation == nil) + { + gPlayerData.playerLocation = [[CLLocation alloc] initWithLatitude: lat longitude: lng]; + } + else + { + [gPlayerData.playerLocation initWithLatitude: lat longitude: lng]; + } + + [self updatePosition: NO]; +} + +-(void)newError:(NSString *)text +{ + isCurrentlyUpdating = NO; + [self updatePosition: NO]; + alertView = [[UIAlertView alloc] initWithTitle: @"Location Services Failed" message: text delegate: self cancelButtonTitle: @"OK" otherButtonTitles: nil]; + [alertView show]; +} + + +@end + + +@implementation MenuController(Private) + +#define TOP_LEADERS_UPDATE_INTERVAL 10*60*1000 +//#define TOP_LEADER_COUNT 5 + +// rotate display of Top 5 in Menu page every 3 seconds +- (void)updateLeaderLabel: (NSTimer*)timer; +{ + static int displayedLeaderNumber = -1; + int maxLeaders = [ gLeaderboardData count ]; + + if( maxLeaders == 0 ) + return; + + ++displayedLeaderNumber; + if( displayedLeaderNumber >= maxLeaders ) + displayedLeaderNumber = 0; + + int secs = ((LeaderboardObject*)[ gLeaderboardData objectAtIndex:displayedLeaderNumber ]).bestRaceTime /1000; + int millis = ((LeaderboardObject*)[ gLeaderboardData objectAtIndex:displayedLeaderNumber ]).bestRaceTime - (secs * 1000); + NSString* leaderName = [((LeaderboardObject*)[gLeaderboardData objectAtIndex: displayedLeaderNumber]).name copy ]; + + leadersPositionLabel.text = [NSString stringWithFormat: @"%d", displayedLeaderNumber+1 ]; + leadersLabel.text = leaderName; //[NSString stringWithFormat: @"%d %s", displayedLeaderNumber+1, leaderName ]; + leadersTimeLabel.text = [NSString stringWithFormat: @"%2d.%03d", secs, millis ]; +} + + +/*- (void)updateLeaderLabel: (NSTimer*)timer; +{ +/* static int currentLeaderNumber = -1; + static uint64_t curTime, lastUpdateTime = nil; + + curTime = mach_absolute_time(); + + if( lastUpdateTime == nil || lastUpdateTime - curTime > TOP_LEADERS_UPDATE_INTERVAL ) + { + // update latest Top 5 leaders + [appDelegate showLeadersByTime ]; + } + + // set last update time + lastUpdateTime = curTime; + + // decide next leader to display + if( currentLeaderNumber == TOP_LEADER_COUNT ) + currentLeaderNumber = -1; + + ++currentLeaderNumber; + + leadersLabel.text = [NSString stringWithFormat: @" %d. %s", currentLeaderNumber, gLeaderboardData.]; +} +*/ + + +@end diff --git a/Samples/TapRace/iphone/MyCLController.h b/Samples/TapRace/iphone/MyCLController.h new file mode 100644 index 00000000..6a0c3a23 --- /dev/null +++ b/Samples/TapRace/iphone/MyCLController.h @@ -0,0 +1,82 @@ +/* + This source was taken from the LocateMe sample application released by APPLE +as part of the iPhone SDK. +*/ + +/* + +File: MyCLController.h +Abstract: Singleton class used to talk to CoreLocation and send results back to +the app's view controllers. + +Version: 1.1 + +Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. +("Apple") in consideration of your agreement to the following terms, and your +use, installation, modification or redistribution of this Apple software +constitutes acceptance of these terms. If you do not agree with these terms, +please do not use, install, modify or redistribute this Apple software. + +In consideration of your agreement to abide by the following terms, and subject +to these terms, Apple grants you a personal, non-exclusive license, under +Apple's copyrights in this original Apple software (the "Apple Software"), to +use, reproduce, modify and redistribute the Apple Software, with or without +modifications, in source and/or binary forms; provided that if you redistribute +the Apple Software in its entirety and without modifications, you must retain +this notice and the following text and disclaimers in all such redistributions +of the Apple Software. +Neither the name, trademarks, service marks or logos of Apple Inc. may be used +to endorse or promote products derived from the Apple Software without specific +prior written permission from Apple. Except as expressly stated in this notice, +no other rights or licenses, express or implied, are granted by Apple herein, +including but not limited to any patent rights that may be infringed by your +derivative works or by other works in which the Apple Software may be +incorporated. + +The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO +WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED +WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN +COMBINATION WITH YOUR PRODUCTS. + +IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR +DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF +CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF +APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Copyright (C) 2008 Apple Inc. All Rights Reserved. + +*/ + + +// This protocol is used to send the text for location updates back to another view controller +@protocol MyCLControllerDelegate +@required +-(void)newLocationUpdateLat:(float)lat Long: (float)lng; +-(void)newError:(NSString *)text; +@end + + +// Class definition +@interface MyCLController : NSObject { + CLLocationManager *locationManager; + id delegate; +} + +@property (nonatomic, retain) CLLocationManager *locationManager; +@property (nonatomic,assign) id delegate; + +- (void)locationManager:(CLLocationManager *)manager + didUpdateToLocation:(CLLocation *)newLocation + fromLocation:(CLLocation *)oldLocation; + +- (void)locationManager:(CLLocationManager *)manager + didFailWithError:(NSError *)error; + ++ (MyCLController *)sharedInstance; + +@end + diff --git a/Samples/TapRace/iphone/MyCLController.mm b/Samples/TapRace/iphone/MyCLController.mm new file mode 100644 index 00000000..9fe44846 --- /dev/null +++ b/Samples/TapRace/iphone/MyCLController.mm @@ -0,0 +1,254 @@ +/* + This source was taken (and modified) from the LocateMe sample application released by APPLE + as part of the iPhone SDK. + */ + +/* + +File: MyCLController.m +Abstract: Singleton class used to talk to CoreLocation and send results back to +the app's view controllers. + +Version: 1.1 + +Disclaimer: IMPORTANT: This Apple software is supplied to you by Apple Inc. +("Apple") in consideration of your agreement to the following terms, and your +use, installation, modification or redistribution of this Apple software +constitutes acceptance of these terms. If you do not agree with these terms, +please do not use, install, modify or redistribute this Apple software. + +In consideration of your agreement to abide by the following terms, and subject +to these terms, Apple grants you a personal, non-exclusive license, under +Apple's copyrights in this original Apple software (the "Apple Software"), to +use, reproduce, modify and redistribute the Apple Software, with or without +modifications, in source and/or binary forms; provided that if you redistribute +the Apple Software in its entirety and without modifications, you must retain +this notice and the following text and disclaimers in all such redistributions +of the Apple Software. +Neither the name, trademarks, service marks or logos of Apple Inc. may be used +to endorse or promote products derived from the Apple Software without specific +prior written permission from Apple. Except as expressly stated in this notice, +no other rights or licenses, express or implied, are granted by Apple herein, +including but not limited to any patent rights that may be infringed by your +derivative works or by other works in which the Apple Software may be +incorporated. + +The Apple Software is provided by Apple on an "AS IS" basis. APPLE MAKES NO +WARRANTIES, EXPRESS OR IMPLIED, INCLUDING WITHOUT LIMITATION THE IMPLIED +WARRANTIES OF NON-INFRINGEMENT, MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE, REGARDING THE APPLE SOFTWARE OR ITS USE AND OPERATION ALONE OR IN +COMBINATION WITH YOUR PRODUCTS. + +IN NO EVENT SHALL APPLE BE LIABLE FOR ANY SPECIAL, INDIRECT, INCIDENTAL OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE +GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) +ARISING IN ANY WAY OUT OF THE USE, REPRODUCTION, MODIFICATION AND/OR +DISTRIBUTION OF THE APPLE SOFTWARE, HOWEVER CAUSED AND WHETHER UNDER THEORY OF +CONTRACT, TORT (INCLUDING NEGLIGENCE), STRICT LIABILITY OR OTHERWISE, EVEN IF +APPLE HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +Copyright (C) 2008 Apple Inc. All Rights Reserved. + +*/ + +#import "MyCLController.h" +#import "AppDelegate.h" + + +// Shorthand for getting localized strings, used in formats below for readability +//#define LocStr(key) [[NSBundle mainBundle] localizedStringForKey:(key) value:@"" table:nil] + + +// This is a singleton class, see below +static MyCLController *sharedCLDelegate = nil; + +@implementation MyCLController + +@synthesize delegate, locationManager; + +- (id) init { + self = [super init]; + if (self != nil) { + self.locationManager = [[[CLLocationManager alloc] init] autorelease]; + self.locationManager.delegate = self; // Tells the location manager to send updates to this object + [self.locationManager startUpdatingLocation]; + } + return self; +} + + +// Called when the location is updated +- (void)locationManager:(CLLocationManager *)manager + didUpdateToLocation:(CLLocation *)newLocation + fromLocation:(CLLocation *)oldLocation +{ + NSLog(@"got cl callback"); + if (delegate == nil) return; + if (signbit(newLocation.horizontalAccuracy)) { + // Negative accuracy means an invalid or unavailable measurement + + // TODO: pop-up message saying no coords available + } else { + // parse the string to extract the lat / long + // format looks like: <+37.33168900, -122.03073100> +/- 100.00m @ 2008-10-21 23:33:05 -0700 + [self.delegate newLocationUpdateLat: newLocation.coordinate.latitude Long: newLocation.coordinate.longitude ]; + } + +/*#if 0 // don't need to get all this data, so commented out. + NSMutableString *update = [[[NSMutableString alloc] init] autorelease]; + + // Timestamp + NSDateFormatter *dateFormatter = [[[NSDateFormatter alloc] init] autorelease]; + [dateFormatter setDateStyle:NSDateFormatterMediumStyle]; + [dateFormatter setTimeStyle:NSDateFormatterMediumStyle]; + [update appendFormat:@"%@\n\n", [dateFormatter stringFromDate:newLocation.timestamp]]; + + // Horizontal coordinates + if (signbit(newLocation.horizontalAccuracy)) { + // Negative accuracy means an invalid or unavailable measurement + [update appendString:LocStr(@"LatLongUnavailable")]; + } else { + // CoreLocation returns positive for North & East, negative for South & West + [update appendFormat:LocStr(@"LatLongFormat"), // This format takes 4 args: 2 pairs of the form coordinate + compass direction + fabs(newLocation.coordinate.latitude), signbit(newLocation.coordinate.latitude) ? LocStr(@"South") : LocStr(@"North"), + fabs(newLocation.coordinate.longitude), signbit(newLocation.coordinate.longitude) ? LocStr(@"West") : LocStr(@"East")]; + [update appendString:@"\n"]; + [update appendFormat:LocStr(@"MeterAccuracyFormat"), newLocation.horizontalAccuracy]; + } + [update appendString:@"\n\n"]; + + // Altitude + if (signbit(newLocation.verticalAccuracy)) { + // Negative accuracy means an invalid or unavailable measurement + [update appendString:LocStr(@"AltUnavailable")]; + } else { + // Positive and negative in altitude denote above & below sea level, respectively + [update appendFormat:LocStr(@"AltitudeFormat"), fabs(newLocation.altitude), (signbit(newLocation.altitude)) ? LocStr(@"BelowSeaLevel") : LocStr(@"AboveSeaLevel")]; + [update appendString:@"\n"]; + [update appendFormat:LocStr(@"MeterAccuracyFormat"), newLocation.verticalAccuracy]; + } + [update appendString:@"\n\n"]; + + // Calculate disatance moved and time elapsed, but only if we have an "old" location + // + // NOTE: Timestamps are based on when queries start, not when they return. CoreLocation will query your + // location based on several methods. Sometimes, queries can come back in a different order from which + // they were placed, which means the timestamp on the "old" location can sometimes be newer than on the + // "new" location. For the example, we will clamp the timeElapsed to zero to avoid showing negative times + // in the UI. + // + if (oldLocation != nil) { + CLLocationDistance distanceMoved = [newLocation getDistanceFrom:oldLocation]; + NSTimeInterval timeElapsed = [newLocation.timestamp timeIntervalSinceDate:oldLocation.timestamp]; + + [update appendFormat:LocStr(@"LocationChangedFormat"), distanceMoved]; + if (signbit(timeElapsed)) { + [update appendString:LocStr(@"FromPreviousMeasurement")]; + } else { + [update appendFormat:LocStr(@"TimeElapsedFormat"), timeElapsed]; + } + [update appendString:@"\n\n"]; + } + + // Send the update to our delegate + [self.delegate newLocationUpdate:update]; +#endif +*/ +} + + +// Called when there is an error getting the location +- (void)locationManager:(CLLocationManager *)manager + didFailWithError:(NSError *)error +{ + if (delegate == nil) return; + + NSMutableString *errorString = [[[NSMutableString alloc] init] autorelease]; + + if ([error domain] == kCLErrorDomain) { + + // We handle CoreLocation-related errors here + + switch ([error code]) { + // This error code is usually returned whenever user taps "Don't Allow" in response to + // being told your app wants to access the current location. Once this happens, you cannot + // attempt to get the location again until the app has quit and relaunched. + // + // "Don't Allow" on two successive app launches is the same as saying "never allow". The user + // can reset this for all apps by going to Settings > General > Reset > Reset Location Warnings. + // + case kCLErrorDenied: + [errorString appendFormat:@"%@\n", NSLocalizedString(@"LocationDenied", nil)]; + break; + + // This error code is usually returned whenever the device has no data or WiFi connectivity, + // or when the location cannot be determined for some other reason. + // + // CoreLocation will keep trying, so you can keep waiting, or prompt the user. + // + case kCLErrorLocationUnknown: + [errorString appendFormat:@"%@\n", NSLocalizedString(@"LocationUnknown", nil)]; + break; + + // We shouldn't ever get an unknown error code, but just in case... + // + default: + [errorString appendFormat:@"%@ %d\n", NSLocalizedString(@"GenericLocationError", nil), [error code]]; + break; + } + } else { + // We handle all non-CoreLocation errors here + // (we depend on localizedDescription for localization) + [errorString appendFormat:@"Error domain: \"%@\" Error code: %d\n", [error domain], [error code]]; + [errorString appendFormat:@"Description: \"%@\"\n", [error localizedDescription]]; + } + + // Send the update to our delegate + [self.delegate newError:errorString]; +} + +#pragma mark ---- singleton object methods ---- + +// See "Creating a Singleton Instance" in the Cocoa Fundamentals Guide for more info + ++ (MyCLController *)sharedInstance { + @synchronized(self) { + if (sharedCLDelegate == nil) { + [[self alloc] init]; // assignment not done here + } + } + return sharedCLDelegate; +} + ++ (id)allocWithZone:(NSZone *)zone { + @synchronized(self) { + if (sharedCLDelegate == nil) { + sharedCLDelegate = [super allocWithZone:zone]; + return sharedCLDelegate; // assignment and return on first allocation + } + } + return nil; // on subsequent allocation attempts return nil +} + +- (id)copyWithZone:(NSZone *)zone +{ + return self; +} + +- (id)retain { + return self; +} + +- (unsigned)retainCount { + return UINT_MAX; // denotes an object that cannot be released +} + +- (void)release { + //do nothing +} + +- (id)autorelease { + return self; +} + +@end \ No newline at end of file diff --git a/Samples/TapRace/iphone/Resources/AboutGamespy.png b/Samples/TapRace/iphone/Resources/AboutGamespy.png new file mode 100644 index 00000000..1c3330a7 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/AboutGamespy.png differ diff --git a/Samples/TapRace/iphone/Resources/Default.png b/Samples/TapRace/iphone/Resources/Default.png new file mode 100644 index 00000000..307f376a Binary files /dev/null and b/Samples/TapRace/iphone/Resources/Default.png differ diff --git a/Samples/TapRace/iphone/Resources/FlipsideView.xib b/Samples/TapRace/iphone/Resources/FlipsideView.xib new file mode 100644 index 00000000..4353b6f4 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/FlipsideView.xib @@ -0,0 +1,416 @@ + + + + 768 + 9L31a + 677 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 274 + + YES + + + 290 + {320, 44} + + NO + NO + 1 + + YES + + + About + + 2 + 0 + + + Contact Us + 1 + + + + + + + 292 + {{52, 44}, {215, 29}} + + + 1 + MCAwIDAgMAA + + NO + YES + NO + TapRace v1.3 + + TrebuchetMS-Bold + 1.800000e+01 + 16 + + + 1 + MSAxIDEAA + + 1 + + + + 1 + 1.800000e+01 + 1 + + + + 292 + {{19, 358}, {280, 82}} + + + 3 + MCAwAA + + NO + YES + 5 + NO + VGhpcyBhcHBsaWNhdGlvbiB3YXMgZGV2ZWxvcGVkIGJ5IApHYW1lU3B5IFRlY2hub2xvZ2llcyB0byBz +aG93Y2FzZSB0aGUgCnBvc3NpYmlsaXRpZXMgdGhyb3VnaCB0aGUgdXNlIG9mIEdhbWVTcHkgClRlY2hu +b2xvZ2llcycgb25saW5lIHNlcnZpY2VzIGFuZCBzb2x1dGlvbnMuA + + TrebuchetMS-Bold + 1.300000e+01 + 16 + + + 1 + MSAxIDEAA + + + 0 + 1.000000e+01 + 4 + 0 + + + + 292 + {{0, 73}, {320, 337}} + + NO + NO + 4 + NO + + NSImage + AboutGamespy.png + + + + + -2147483356 + {{124, 160}, {72, 37}} + + NO + NO + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + 1 + Clear + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + {320, 460} + + + 1 + MCAwIDAAA + + NO + + 2 + + + + + + YES + + + view + + + + 41 + + + + done + + + + 46 + + + + contactUs: + + + + 51 + + + + clearBlockedImagesFile + + + 7 + + 54 + + + + + YES + + 0 + + YES + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 40 + + + YES + + + + + + + + + + 42 + + + YES + + + + + + 43 + + + YES + + + + + + + 44 + + + + + 47 + + + + + 48 + + + + + 49 + + + + + 52 + + + + + 53 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 40.CustomClassName + 40.IBEditorWindowLastContentRect + 40.IBPluginDependency + 42.IBPluginDependency + 43.IBPluginDependency + 44.IBPluginDependency + 47.IBPluginDependency + 48.IBPluginDependency + 49.IBPluginDependency + 52.IBPluginDependency + 53.IBPluginDependency + + + YES + FlipsideViewController + UIResponder + FlipsideView + {{663, 63}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 54 + + + + YES + + FlipsideView + UIView + + IBProjectSource + Samples/TapRace/iphone/FlipsideView.h + + + + FlipsideViewController + UIViewController + + YES + + YES + clearBlockedImagesFile + contactUs: + done + + + YES + id + id + id + + + + YES + + YES + clearBlockedImagesButton + delegate + + + YES + UIButton + id + + + + IBProjectSource + Samples/TapRace/iphone/FlipsideViewController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/Gamespy.png b/Samples/TapRace/iphone/Resources/Gamespy.png new file mode 100644 index 00000000..c1c3e3d1 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/Gamespy.png differ diff --git a/Samples/TapRace/iphone/Resources/Globe.png b/Samples/TapRace/iphone/Resources/Globe.png new file mode 100644 index 00000000..9e40232c Binary files /dev/null and b/Samples/TapRace/iphone/Resources/Globe.png differ diff --git a/Samples/TapRace/iphone/Resources/MainWindow.xib b/Samples/TapRace/iphone/Resources/MainWindow.xib new file mode 100644 index 00000000..c69338fe --- /dev/null +++ b/Samples/TapRace/iphone/Resources/MainWindow.xib @@ -0,0 +1,336 @@ + + + + 528 + 9J61 + 677 + 949.46 + 353.00 + + YES + + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + + 1316 + + {320, 480} + + + 1 + MSAxIDEAA + + NO + NO + + + + + + + 256 + {0, 0} + NO + YES + YES + + + YES + + Login + + + Login + + Logout + 1 + + + + login + + + + + + + YES + + + window + + + + 4 + + + + navController + + + + 9 + + + + delegate + + + + 10 + + + + delegate + + + + 12 + + + + + YES + + 0 + + YES + + + + + + 2 + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 3 + + + + + 5 + + + YES + + + + + + + 6 + + + YES + + + + + + 7 + + + + + 8 + + + YES + + + + + + 13 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 2.IBAttributePlaceholdersKey + 2.IBEditorWindowLastContentRect + 2.IBPluginDependency + 2.UIWindow.visibleAtLaunch + 3.CustomClassName + 3.IBPluginDependency + 5.IBEditorWindowLastContentRect + 5.IBPluginDependency + 6.CustomClassName + 6.IBPluginDependency + 7.IBPluginDependency + 8.IBPluginDependency + + + YES + UIApplication + UIResponder + + YES + + YES + + + YES + + + {{493, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + AppDelegate + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{1400, 376}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + LoginController + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 13 + + + + YES + + AppDelegate + NSObject + + YES + + YES + navController + window + + + YES + UINavigationController + UIWindow + + + + IBProjectSource + Samples/TapRace/iphone/AppDelegate.h + + + + LoginController + UIViewController + + YES + + YES + autoLoginChanged: + createClicked: + forgotPasswordClicked: + loginClicked: + returnClicked: + + + YES + id + id + id + id + id + + + + YES + + YES + activityIndicator + autoLoginSwitch + forgotPassword + info + passwordField + userNameField + userNamePicker + userNamePickerView + + + YES + UIActivityIndicatorView + UISwitch + UIButton + UIButton + UITextField + UITextField + UIPickerView + UIView + + + + IBProjectSource + Samples/TapRace/iphone/LoginController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/Top100.jpg b/Samples/TapRace/iphone/Resources/Top100.jpg new file mode 100644 index 00000000..800a127c Binary files /dev/null and b/Samples/TapRace/iphone/Resources/Top100.jpg differ diff --git a/Samples/TapRace/iphone/Resources/UserStats.xib b/Samples/TapRace/iphone/Resources/UserStats.xib new file mode 100644 index 00000000..b163b1b3 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/UserStats.xib @@ -0,0 +1,913 @@ + + + + 528 + 9L31a + 677 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + User Stats + + + + 292 + + YES + + + 290 + + YES + + + 292 + {{20, 20}, {75, 93}} + + NO + NO + 4 + NO + + NSImage + singlePlayer.png + + + + + 292 + {{103, 14}, {198, 37}} + + NO + YES + NO + Leader + + TrebuchetMS-Bold + 3.000000e+01 + 16 + + + 1 + MSAxIDEAA + + + 1 + MCAwIDEgMAA + + + 1 + MCAwIDAAA + + 1 + 1.000000e+01 + 1 + + + + 292 + {{103, 51}, {198, 34}} + + NO + YES + NO + Stats + + + + 1 + MCAwIDEgMAA + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{40, 183}, {48, 19}} + + NO + YES + NO + Races: + + TrebuchetMS-Bold + 1.400000e+01 + 16 + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{93, 184}, {37, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{47, 210}, {41, 19}} + + NO + YES + NO + Wins: + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{96, 211}, {35, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{187, 210}, {54, 19}} + + NO + YES + NO + Losses: + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{249, 211}, {37, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + + 292 + {{12, 238}, {76, 19}} + + NO + YES + NO + Win Ratio: + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{93, 239}, {38, 19}} + + NO + YES + NO + 100% + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{165, 156}, {76, 19}} + + NO + YES + NO + Best Time: + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{249, 156}, {53, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + + 292 + {{162, 238}, {79, 19}} + + NO + YES + NO + Win Streak: + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{249, 239}, {36, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + + 292 + {{249, 183}, {47, 19}} + + NO + YES + NO + #5999 + + + + 1 + 1.000000e+01 + + + + 292 + {{197, 183}, {44, 19}} + + NO + YES + NO + Rank: + + + + 0 + 1.000000e+01 + 2 + + + + 265 + {{12, 121}, {90, 30}} + + NO + NO + 0 + 0 + + 1 + Report Image + Report Image + Report Image + Block Image + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MAA + + + + {320, 274} + + + NO + NO + + + + 269 + {{0, 344}, {320, 76}} + + NO + NO + 7 + NO + + NSImage + gamespyFooter.png + + + + + -2147483380 + {{20, 282}, {120, 50}} + + NO + NO + 0 + 0 + + TrebuchetMS-Bold + 1.800000e+01 + 16 + + 1 + Make Buddy + Make Buddy + Make Buddy + Make Buddy + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + 265 + {{270, 372}, {18, 19}} + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + 3 + YES + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + {320, 416} + + + + + NO + + + + + + YES + + + view + + + + 15 + + + + makeBuddyButton + + + + 17 + + + + reportImageButton + + + + 18 + + + + makeBuddyButtonClicked: + + + 7 + + 19 + + + + reportImageButtonClicked: + + + 7 + + 20 + + + + userName + + + + 21 + + + + thumbNail + + + + 22 + + + + userTotalRaces + + + + 27 + + + + userTotalWins + + + + 33 + + + + userTotalLosses + + + + 38 + + + + userBestTime + + + + 47 + + + + userWinStreak + + + + 52 + + + + userRatio + + + + 54 + + + + showInfo + + + 7 + + 60 + + + + myRank + + + + 63 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 3 + + + YES + + + + + + + + + + + + + + + + + + + + + + + 4 + + + + + 11 + + + + + 13 + + + + + 14 + + + + + 23 + + + + + 25 + + + userNbrRaces + + + 30 + + + + + 31 + + + userNbrWins + + + 35 + + + + + 36 + + + userNbrLosses + + + 40 + + + + + 41 + + + userRatio + + + 44 + + + + + 45 + + + userBestTime + + + 49 + + + + + 50 + + + userWinStreak + + + 7 + + + + + 59 + + + + + 61 + + + myWinStreak + + + 62 + + + + + 8 + + + + + 64 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 11.IBPluginDependency + 13.IBPluginDependency + 14.IBPluginDependency + 23.IBPluginDependency + 25.IBPluginDependency + 3.IBPluginDependency + 30.IBPluginDependency + 31.IBPluginDependency + 35.IBPluginDependency + 36.IBPluginDependency + 4.IBPluginDependency + 40.IBPluginDependency + 41.IBPluginDependency + 44.IBPluginDependency + 45.IBPluginDependency + 49.IBPluginDependency + 50.IBPluginDependency + 59.IBPluginDependency + 61.IBPluginDependency + 62.IBPluginDependency + 64.IBPluginDependency + 7.IBPluginDependency + 8.IBPluginDependency + + + YES + UserStatsController + UIResponder + {{451, 185}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 64 + + + + YES + + UserStatsController + UIViewController + + YES + + YES + makeBuddyButtonClicked: + reportImageButtonClicked: + showInfo + + + YES + id + id + id + + + + YES + + YES + makeBuddyButton + myBestTime + myRank + myRatio + myTotalLosses + myTotalRaces + myTotalWins + myWinStreak + reportImageButton + thumbNail + userBestTime + userName + userRatio + userTotalLosses + userTotalRaces + userTotalWins + userWinStreak + + + YES + UIButton + UILabel + UILabel + UILabel + UILabel + UILabel + UILabel + UILabel + UIButton + UIImageView + UILabel + UILabel + UILabel + UILabel + UILabel + UILabel + UILabel + + + + IBProjectSource + Samples/TapRace/iphone/UserStatsController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/btn_down.png b/Samples/TapRace/iphone/Resources/btn_down.png new file mode 100644 index 00000000..59a446a6 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/btn_down.png differ diff --git a/Samples/TapRace/iphone/Resources/btn_up.png b/Samples/TapRace/iphone/Resources/btn_up.png new file mode 100644 index 00000000..8cbd3409 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/btn_up.png differ diff --git a/Samples/TapRace/iphone/Resources/buddies.png b/Samples/TapRace/iphone/Resources/buddies.png new file mode 100644 index 00000000..fd9799d6 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/buddies.png differ diff --git a/Samples/TapRace/iphone/Resources/buddies_rematch.png b/Samples/TapRace/iphone/Resources/buddies_rematch.png new file mode 100644 index 00000000..7184c982 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/buddies_rematch.png differ diff --git a/Samples/TapRace/iphone/Resources/buddyListCell.xib b/Samples/TapRace/iphone/Resources/buddyListCell.xib new file mode 100644 index 00000000..91421ea5 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/buddyListCell.xib @@ -0,0 +1,359 @@ + + + + 512 + 9L31a + 677 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 296 + + YES + + + 300 + {{37, 8}, {50, 54}} + + NO + 1 + NO + + NSImage + singlePlayer.png + + + + + 292 + {{95, 8}, {165, 18}} + + + 1 + MSAxIDEAA + + YES + NO + Name + + TrebuchetMS + 1.600000e+01 + 16 + + + 1 + MCAwIDAAA + + + 1 + NO + 1.000000e+01 + + + + 292 + {{5, 20}, {24, 26}} + + + YES + NO + 99 + + TrebuchetMS-Bold + 2.400000e+01 + 16 + + + + 1 + 1.000000e+01 + + + + 292 + {{185, 42}, {75, 20}} + + + YES + NO + 12:34.56 + + TrebuchetMS-Bold + 1.800000e+01 + 16 + + + + 1 + 1.000000e+01 + + + + 292 + {{162, 38}, {20, 20}} + + NO + NO + NO + YES + + + + 292 + {{52, 30}, {20, 20}} + + NO + NO + NO + NO + 2 + + + {320, 70} + + + NO + + + + + YES + + + indexLabel + + + + 33 + + + + nameLabel + + + + 34 + + + + thumbnail + + + + 35 + + + + timeLabel + + + + 36 + + + + cellView + + + + 37 + + + + busy + + + + 41 + + + + + YES + + 0 + + YES + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 18 + + + YES + + + + + + + + + + + 8 + + + Time + + + 7 + + + Index + + + 5 + + + Name + + + 3 + + + + + 38 + + + + + 40 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 18.IBEditorWindowLastContentRect + 18.IBPluginDependency + 18.showNotes + 3.IBPluginDependency + 38.IBPluginDependency + 40.IBPluginDependency + 5.IBPluginDependency + 7.IBPluginDependency + 8.IBPluginDependency + + + YES + BuddyListCellView + UIResponder + {{559, 786}, {320, 70}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 41 + + + + YES + + BuddyListCellView + UIView + + YES + + YES + busy + cellView + indexLabel + nameLabel + thumbnail + timeLabel + + + YES + UIActivityIndicatorView + UIView + UILabel + UILabel + UIImageView + UILabel + + + + IBProjectSource + Samples/TapRace/iphone/BuddyListCellView.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/camera.png b/Samples/TapRace/iphone/Resources/camera.png new file mode 100644 index 00000000..6895aaf1 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/camera.png differ diff --git a/Samples/TapRace/iphone/Resources/createuser.xib b/Samples/TapRace/iphone/Resources/createuser.xib new file mode 100644 index 00000000..851deff3 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/createuser.xib @@ -0,0 +1,557 @@ + + + + 528 + 9L31a + 677 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 274 + + YES + + + 292 + {{20, 20}, {119, 21}} + + NO + YES + NO + E-mail Address + + TrebuchetMS-Bold + 1.700000e+01 + 16 + + + 2 + MC4yMTE3NjQ3MiAwLjQ0NzA1ODg2IDAuODU4ODIzNgA + + + 1 + 1.000000e+01 + + + + 290 + {{20, 43}, {280, 31}} + + NO + NO + 0 + + 3 + + 3 + MAA + + 2 + + + YES + 1.700000e+01 + + 1 + 7 + + 3 + + + + 292 + {{20, 89}, {151, 21}} + + NO + YES + NO + Desired User Name + + + 2 + MC4yMDAwMDAwMiAwLjQzNTI5NDE1IDAuODI3NDUxMDUAA + + + 1 + 1.000000e+01 + + + + 290 + {{20, 112}, {280, 31}} + + NO + NO + 0 + + 3 + + 3 + MAA + + + YES + 1.700000e+01 + + 1 + + 3 + + + + 292 + {{20, 159}, {75, 21}} + + NO + YES + NO + Password + + + 2 + MC4xODQzMTM3MyAwLjQwMDAwMDA0IDAuNzcyNTQ5MDkAA + + + 1 + 1.000000e+01 + + + + 290 + {{20, 183}, {280, 31}} + + NO + NO + 0 + + 3 + + 3 + MAA + + + YES + YES + 1.700000e+01 + + 1 + YES + + 3 + + + + 292 + {{20, 222}, {94, 27}} + + NO + YES + YES + 0 + 0 + YES + + + + 292 + {{122, 225}, {84, 21}} + + NO + YES + NO + Auto-login + + + 2 + MC4yMTE3NjQ3MiAwLjQ0NzA1ODg2IDAuODU4ODIzNgA + + + 1 + 1.000000e+01 + + + + 269 + {{0, 385}, {320, 75}} + + NO + NO + 7 + NO + + NSImage + gamespyFooter.png + + + + + 269 + {{100, 327}, {119, 50}} + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + 1 + Create + Create + Create + Create + + 1 + MSAxIDEAA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MAA + + + + {320, 460} + + + 1 + MCAwIDAAA + + + + + + YES + + + view + + + + 13 + + + + emailField + + + + 14 + + + + passwordField + + + + 15 + + + + userNameField + + + + 16 + + + + returnClicked: + + + 20 + + 18 + + + + returnClicked: + + + 20 + + 19 + + + + returnClicked: + + + 20 + + 20 + + + + createButtonClicked: + + + 7 + + 21 + + + + delegate + + + + 22 + + + + delegate + + + + 23 + + + + delegate + + + + 24 + + + + autoLoginSwitch + + + + 26 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + 9 + + + + + 10 + + + Remember Switch + + + 11 + + + + + 12 + + + + + 17 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 10.IBPluginDependency + 11.IBPluginDependency + 12.IBPluginDependency + 17.IBPluginDependency + 4.IBPluginDependency + 5.IBPluginDependency + 6.IBPluginDependency + 7.IBPluginDependency + 8.IBPluginDependency + 9.IBPluginDependency + + + YES + CreateUserController + UIResponder + {{409, 192}, {320, 460}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 26 + + + + YES + + CreateUserController + UIViewController + + YES + + YES + createButtonClicked: + returnClicked: + + + YES + id + id + + + + YES + + YES + autoLoginSwitch + emailField + passwordField + userNameField + + + YES + UISwitch + UITextField + UITextField + UITextField + + + + IBProjectSource + Samples/TapRace/iphone/CreateUserController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/forgotPassword.xib b/Samples/TapRace/iphone/Resources/forgotPassword.xib new file mode 100644 index 00000000..9a1c98a3 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/forgotPassword.xib @@ -0,0 +1,185 @@ + + + + 768 + 9J61 + 677 + 949.46 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 292 + + YES + + + 274 + {320, 480} + + + 3 + MQA + + YES + YES + YES + + + {320, 480} + + + 3 + MQA + + 2 + + + + + + + YES + + + webView + + + + 4 + + + + view + + + + 5 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 3 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 3.IBPluginDependency + + + YES + ForgotPassword + UIResponder + {{426, 160}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 5 + + + + YES + + ForgotPassword + UIViewController + + webView + UIWebView + + + IBProjectSource + Samples/TapRace/iphone/ForgotPassword.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/gamespyFooter.png b/Samples/TapRace/iphone/Resources/gamespyFooter.png new file mode 100644 index 00000000..05a81ad6 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/gamespyFooter.png differ diff --git a/Samples/TapRace/iphone/Resources/gs_logo.gif b/Samples/TapRace/iphone/Resources/gs_logo.gif new file mode 100644 index 00000000..e0cac1e9 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/gs_logo.gif differ diff --git a/Samples/TapRace/iphone/Resources/icon_29x29.png b/Samples/TapRace/iphone/Resources/icon_29x29.png new file mode 100644 index 00000000..98bf923e Binary files /dev/null and b/Samples/TapRace/iphone/Resources/icon_29x29.png differ diff --git a/Samples/TapRace/iphone/Resources/icon_57x57.png b/Samples/TapRace/iphone/Resources/icon_57x57.png new file mode 100644 index 00000000..6a31c56d Binary files /dev/null and b/Samples/TapRace/iphone/Resources/icon_57x57.png differ diff --git a/Samples/TapRace/iphone/Resources/leaderboard.png b/Samples/TapRace/iphone/Resources/leaderboard.png new file mode 100644 index 00000000..a92ae080 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/leaderboard.png differ diff --git a/Samples/TapRace/iphone/Resources/leaderboardMap.png b/Samples/TapRace/iphone/Resources/leaderboardMap.png new file mode 100644 index 00000000..e34b012d Binary files /dev/null and b/Samples/TapRace/iphone/Resources/leaderboardMap.png differ diff --git a/Samples/TapRace/iphone/Resources/leaderboards.xib b/Samples/TapRace/iphone/Resources/leaderboards.xib new file mode 100644 index 00000000..aad126e6 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/leaderboards.xib @@ -0,0 +1,1352 @@ + + + + 528 + 9L31a + 677 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + Leaderboards + + + + 292 + + YES + + + 290 + + YES + + + 292 + {{9, 9}, {37, 19}} + + NO + YES + NO + Races: + + TrebuchetMS-Bold + 1.400000e+01 + 16 + + + 1 + MSAxIDEAA + + + 0 + 1.000000e+01 + + + + 292 + {{48, 9}, {68, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + + 292 + {{9, 32}, {61, 19}} + + NO + YES + NO + Best Time: + + + + 1 + 1.000000e+01 + + + + 292 + {{72, 32}, {57, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + + 292 + {{9, 55}, {61, 19}} + + NO + YES + NO + Avg Time: + + + + 1 + 1.000000e+01 + + + + 292 + {{72, 55}, {57, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + {{0, 154}, {129, 78}} + + + 1 + MCAwIDAAA + + NO + + + + 269 + {{0, 345}, {320, 71}} + + NO + NO + 7 + NO + + NSImage + gamespyFooter.png + + + + + 293 + {{194, 248}, {92, 92}} + + NO + 1 + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MAA + + + NSImage + leaderboard.png + + + + + -2147483356 + {{7, 314}, {92, 92}} + + NO + NO + 0 + 0 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + 293 + {{34, 248}, {92, 92}} + + NO + 1 + 0 + 0 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + NSImage + leaderboardMap.png + + + + + + 292 + + YES + + + 292 + {{9, 4}, {45, 19}} + + NO + YES + NO + Races: + + + + 0 + 1.000000e+01 + + + + 292 + {{59, 4}, {41, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + + 292 + {{9, 27}, {34, 19}} + + NO + YES + NO + Wins: + + + + 1 + 1.000000e+01 + + + + 292 + {{45, 27}, {42, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + + 292 + {{95, 27}, {43, 19}} + + NO + YES + NO + Losses: + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{140, 27}, {42, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + + 292 + {{9, 50}, {62, 19}} + + NO + YES + NO + Win Ratio: + + TrebuchetMS-Bold + 1.600000e+01 + 16 + + + + 1 + 1.000000e+01 + + + + 292 + {{73, 50}, {46, 19}} + + NO + YES + NO + 100% + + + + 1 + 1.000000e+01 + + + + 292 + {{9, 73}, {78, 19}} + + NO + YES + NO + LWin Streak: + + + + 1 + 1.000000e+01 + + + + 292 + {{89, 73}, {56, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + + 292 + {{9, 96}, {62, 19}} + + NO + YES + NO + Best Time: + + + + 1 + 1.000000e+01 + + + + 292 + {{73, 96}, {58, 19}} + + NO + YES + NO + 9999 + + + + 1 + 1.000000e+01 + + + + 292 + {{90, 119}, {49, 21}} + + NO + YES + NO + #5999 + + + + 1 + 1.000000e+01 + + + + 292 + {{9, 119}, {79, 21}} + + NO + YES + NO + Global Rank: + + + + 0 + 1.000000e+01 + + + {{131, 89}, {186, 143}} + + + NO + + + + 292 + {{59, 35}, {37, 37}} + + NO + YES + NO + NO + YES + 0 + + + + 289 + {{40, 2}, {75, 93}} + + NO + 4 + NO + + NSImage + singlePlayer.png + + + + + 289 + {{25, 102}, {21, 18}} + + + 3 + MCAwAA + + YES + NO + 1 + + + + 0 + 0 + + + + + + NSImage + camera.png + + + + + 292 + {{49, 103}, {72, 18}} + + NO + YES + NO + upload a photo + + TrebuchetMS-Bold + 1.000000e+01 + 16 + + + 2 + MC4wMzUyOTQxMTkgMC43Njg2Mjc1MiAwAA + + + 1 + 1.000000e+01 + 8 + + + + 292 + {{150, 7}, {122, 47}} + + NO + YES + NO + My Stats + + TrebuchetMS-Bold + 3.000000e+01 + 16 + + + + + 0 + 1.000000e+01 + + + + 292 + {{6, 129}, {25, 25}} + + NO + NO + 2 + NO + + + + + 292 + {{35, 131}, {86, 21}} + + + 1 + MCAwIDEgMAA + + NO + YES + NO + Single Races + + TrebuchetMS-Bold + 1.700000e+01 + 16 + + + + + 1 + MCAwIDEAA + + 1 + 1.000000e+01 + + + + 292 + {{169, 68}, {124, 21}} + + NO + YES + NO + Two Player Races + + + + + 1 + MSAwIDAAA + + 1 + 1.000000e+01 + + + + 292 + {{141, 68}, {25, 21}} + + NO + NO + 2 + NO + + NSImage + buddies.png + + + + + 292 + {{2, 235}, {316, 3}} + + + NO + YES + NO + + + 1 + MSAxIDEAA + + 1 + + + + 1 + 1.000000e+01 + + + + 265 + {{270, 371}, {18, 19}} + + NO + NO + 0 + 0 + + 3 + YES + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + {320, 416} + + + + + NO + + + + + + YES + + + byLocationButton + + + + 18 + + + + mySpotButton + + + + 19 + + + + setPhotoButton + + + + 20 + + + + top100Button + + + + 21 + + + + view + + + + 22 + + + + byLocationButtonClicked: + + + 7 + + 23 + + + + setPhotoButtonClicked: + + + 7 + + 25 + + + + top100ButtonClicked: + + + 7 + + 26 + + + + myTotalRaces + + + + 45 + + + + myBestTime + + + + 46 + + + + myRatio + + + + 47 + + + + myTotalLosses + + + + 48 + + + + myTotalWins + + + + 49 + + + + thumbNail + + + + 51 + + + + busy + + + + 55 + + + + mySPTotalRaces + + + + 76 + + + + mySPBestTime + + + + 77 + + + + myStreakLabel + + + + 78 + + + + myStreak + + + + 79 + + + + myRank + + + + 82 + + + + mySPAvgTime + + + + 89 + + + + showInfo + + + 7 + + 92 + + + + myBuddiesButtonClicked: + + + 7 + + 93 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + + + + + + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 6 + + + YES + + + + + + + + + + + 7 + + + + + 16 + + + YES + + + + + 13 + + + + + 15 + + + + + 14 + + + + + 75 + + + YES + + + + + + + + + + + + + + + + + + + 27 + + + + + 39 + + + myNbrRaces + + + 28 + + + + + 40 + + + myNbrWins + + + 29 + + + + + 41 + + + myNbrLosses + + + 30 + + + + + 42 + + + myRatio + + + 69 + + + + + 70 + + + myNbrRaces + + + 67 + + + + + 68 + + + myBestTime + + + 32 + + + + + 44 + + + myWinStreak + + + 31 + + + + + 43 + + + myBestTime + + + 83 + + + + + 84 + + + myBestTime + + + 54 + + + busy + + + 11 + + + + + 12 + + + + + 60 + + + + + 86 + + + + + 73 + + + + + 71 + + + + + 3 + + + + + 72 + + + + + 90 + + + + + 91 + + + + + 81 + + + myWinStreak + + + 85 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 1.IBViewEditorWindowController.showingBoundsRectangles + 1.IBViewEditorWindowController.showingLayoutRectangles + 11.IBPluginDependency + 12.IBPluginDependency + 13.IBPluginDependency + 14.IBPluginDependency + 15.IBPluginDependency + 16.IBPluginDependency + 27.IBPluginDependency + 28.IBPluginDependency + 29.IBPluginDependency + 3.IBPluginDependency + 30.IBPluginDependency + 31.IBPluginDependency + 32.IBPluginDependency + 39.IBPluginDependency + 40.IBPluginDependency + 41.IBPluginDependency + 42.IBPluginDependency + 43.IBPluginDependency + 44.IBPluginDependency + 54.IBPluginDependency + 6.IBPluginDependency + 60.IBPluginDependency + 67.IBPluginDependency + 68.IBPluginDependency + 69.IBPluginDependency + 7.IBPluginDependency + 70.IBPluginDependency + 71.IBPluginDependency + 72.IBPluginDependency + 73.IBPluginDependency + 75.IBPluginDependency + 81.IBPluginDependency + 83.IBPluginDependency + 84.IBPluginDependency + 85.IBPluginDependency + 86.IBPluginDependency + 90.IBPluginDependency + 91.IBPluginDependency + + + YES + LeaderboardsController + UIResponder + {{829, 220}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 93 + + + + YES + + LeaderboardsController + UIViewController + + YES + + YES + byLocationButtonClicked: + myBuddiesButtonClicked: + setPhotoButtonClicked: + showInfo + top100ButtonClicked: + + + YES + id + id + id + id + id + + + + YES + + YES + busy + byLocationButton + myBestTime + myRank + myRatio + mySPAvgTime + mySPBestTime + mySPTotalRaces + mySpotButton + myStreak + myStreakLabel + myTotalLosses + myTotalRaces + myTotalWins + setPhotoButton + thumbNail + top100Button + + + YES + UIActivityIndicatorView + UIButton + UILabel + UILabel + UILabel + UILabel + UILabel + UILabel + UIButton + UILabel + UILabel + UILabel + UILabel + UILabel + UIButton + UIImageView + UIButton + + + + IBProjectSource + Samples/TapRace/iphone/LeaderboardsController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/leadersbylocation.xib b/Samples/TapRace/iphone/Resources/leadersbylocation.xib new file mode 100644 index 00000000..897ccb40 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/leadersbylocation.xib @@ -0,0 +1,314 @@ + + + + 768 + 9J61 + 677 + 949.46 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + Leaders By Location + + + + 292 + + YES + + + 269 + {{0, 343}, {320, 76}} + + NO + YES + NO + 4 + NO + + NSImage + gamespyFooter.png + + + + + 265 + {{271, 371}, {18, 19}} + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + 3 + YES + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + + 292 + {{141, 165}, {37, 37}} + + NO + NO + NO + YES + 0 + + + + 268 + {{0, -1}, {320, 346}} + + NO + YES + 4 + YES + + + {320, 416} + + + 1 + MCAwIDAAA + + YES + + + NO + + + + + + YES + + + view + + + + 7 + + + + spinner + + + + 16 + + + + delegate + + + + 19 + + + + mapView + + + + 20 + + + + showInfo + + + 7 + + 22 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 4 + + + + + 6 + + + + + 15 + + + spinner + + + 18 + + + + + 21 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 15.IBPluginDependency + 18.IBPluginDependency + 21.IBPluginDependency + 4.IBPluginDependency + 6.IBPluginDependency + + + YES + LeadersByLocationController + UIResponder + {{432, 21}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 22 + + + + YES + + LeadersByLocationController + UIViewController + + showInfo + id + + + YES + + YES + mapView + spinner + webView + + + YES + MKMapView + UIActivityIndicatorView + UIWebView + + + + IBProjectSource + Samples/TapRace/iphone/LeadersByLocationController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/leadersbytime.xib b/Samples/TapRace/iphone/Resources/leadersbytime.xib new file mode 100644 index 00000000..4440bf05 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/leadersbytime.xib @@ -0,0 +1,274 @@ + + + + 528 + 9J61 + 677 + 949.46 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + Leaders by Time + + + + 292 + + YES + + + 269 + {{0, 345}, {320, 72}} + + NO + NO + 7 + NO + + NSImage + gamespyFooter.png + + + + + 265 + {{270, 371}, {18, 19}} + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + 3 + YES + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + + 292 + {{13, 12}, {293, 325}} + + NO + YES + NO + Loading... + + 1 + MCAwIDAAA + + + 1 + 1.000000e+01 + 1 + + + {320, 416} + + + 1 + MSAxIDEAA + + + + NO + + + + + + YES + + + view + + + + 8 + + + + tableContainer + + + + 18 + + + + showInfo + + + 7 + + 20 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 5 + + + + + 6 + + + + + 17 + + + tableContainer + + + 19 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 17.IBPluginDependency + 19.IBPluginDependency + 5.IBPluginDependency + 6.IBPluginDependency + + + YES + LeadersController + UIResponder + {{423, 91}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 20 + + + + YES + + LeadersController + UIViewController + + showInfo + id + + + tableContainer + UILabel + + + IBProjectSource + Samples/TapRace/iphone/LeadersController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/login.xib b/Samples/TapRace/iphone/Resources/login.xib new file mode 100644 index 00000000..6c94ce8d --- /dev/null +++ b/Samples/TapRace/iphone/Resources/login.xib @@ -0,0 +1,699 @@ + + + + 768 + 9L31a + 677 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 274 + + YES + + + 292 + {{20, 16}, {86, 21}} + + NO + YES + NO + User Name + + TrebuchetMS-Bold + 1.700000e+01 + 16 + + + 2 + MC4xNDUwOTgwNSAwLjQwMzkyMTYgMC43ODQzMTM4AA + + + 1 + 1.000000e+01 + + + + 292 + {{20, 84}, {75, 21}} + + NO + YES + NO + Password + + + 2 + MC4xMjE1Njg2NCAwLjM2MDc4NDMyIDAuNzEzNzI1NTEAA + + + 1 + 1.000000e+01 + + + + 292 + {{20, 147}, {94, 27}} + + NO + YES + YES + 0 + 0 + + + + 292 + {{122, 150}, {84, 21}} + + NO + YES + NO + Auto-login + + + 2 + MC4xMjE1Njg2NCAwLjM2MDc4NDMyIDAuNzEzNzI1NTEAA + + + 1 + 1.000000e+01 + + + + 290 + {{20, 45}, {280, 31}} + + NO + NO + 0 + + 3 + + 3 + MAA + + 2 + + + YES + 1.700000e+01 + + 1 + + 3 + + + + 290 + {{20, 108}, {280, 31}} + + NO + NO + 0 + + 3 + + 3 + MAA + + + YES + YES + 1.700000e+01 + + 1 + YES + + 3 + + + + 269 + {{0, 350}, {320, 66}} + + NO + NO + 4 + NO + + NSImage + gamespyFooter.png + + + + + 268 + {{20, 292}, {128, 50}} + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.000000e+01 + 16 + + 1 + Forgot Password + Forgot Password + Forgot Password + Forgot Password + + 1 + MSAxIDEAA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MAA + + + + + 289 + {{95, 234}, {128, 50}} + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + 1 + Login + Login + Login + Login + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + -2147483359 + {{141, 172}, {37, 37}} + + NO + NO + NO + 0 + + + + 268 + {{172, 292}, {128, 50}} + + NO + NO + 0 + 0 + + 1 + Create Account + Create Account + Create Account + Create Account + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + -2147483374 + + YES + + + -2147483356 + {{-141, -21}, {320, 216}} + + NO + YES + YES + YES + + + {{141, 20}, {156, 0}} + + + 3 + MCAwLjUAA + + NO + NO + + + {320, 416} + + + 1 + MCAwIDAAA + + YES + + + NO + + + + + + YES + + + view + + + + 37 + + + + passwordField + + + + 39 + + + + returnClicked: + + + 20 + + 41 + + + + returnClicked: + + + 20 + + 42 + + + + loginClicked: + + + 7 + + 43 + + + + activityIndicator + + + + 50 + + + + userNameField + + + + 51 + + + + createClicked: + + + 7 + + 53 + + + + delegate + + + + 54 + + + + delegate + + + + 55 + + + + forgotPasswordClicked: + + + 7 + + 56 + + + + userNamePicker + + + + 58 + + + + dataSource + + + + 59 + + + + delegate + + + + 60 + + + + userNamePickerView + + + + 63 + + + + autoLoginChanged: + + + 13 + + 73 + + + + autoLoginSwitch + + + + 74 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 4 + + + + + 5 + + + + + 7 + + + + + 8 + + + + + 9 + + + + + 10 + + + + + 13 + + + + + 17 + + + + + 49 + + + + + 52 + + + + + 61 + + + YES + + + + + + 57 + + + + + 16 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 10.IBPluginDependency + 13.IBPluginDependency + 16.IBPluginDependency + 17.IBPluginDependency + 4.IBPluginDependency + 49.IBPluginDependency + 5.IBPluginDependency + 52.IBPluginDependency + 57.IBPluginDependency + 61.IBEditorWindowLastContentRect + 61.IBPluginDependency + 7.IBPluginDependency + 8.IBPluginDependency + 9.IBPluginDependency + + + YES + LoginController + UIResponder + {{356, 149}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + {{90, 195}, {320, 460}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 76 + + + + YES + + LoginController + UIViewController + + YES + + YES + autoLoginChanged: + createClicked: + forgotPasswordClicked: + loginClicked: + returnClicked: + + + YES + id + id + id + id + id + + + + YES + + YES + activityIndicator + autoLoginSwitch + forgotPassword + info + passwordField + userNameField + userNamePicker + userNamePickerView + + + YES + UIActivityIndicatorView + UISwitch + UIButton + UIButton + UITextField + UITextField + UIPickerView + UIView + + + + IBProjectSource + Samples/TapRace/iphone/LoginController.h + + + + LoginController + UIViewController + + myOutlet1 + id + + + IBUserSource + + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/matchmaking.xib b/Samples/TapRace/iphone/Resources/matchmaking.xib new file mode 100644 index 00000000..49d1fe83 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/matchmaking.xib @@ -0,0 +1,519 @@ + + + + 528 + 9J61 + 677 + 949.46 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + Matchmaking + + + + 274 + + YES + + + 292 + {{20, 20}, {110, 21}} + + NO + YES + NO + Choose Game + + 1 + MCAwIDAAA + + + 1 + 1.000000e+01 + + + + 293 + {{-1, 75}, {320, 216}} + + NO + YES + YES + YES + + + + 265 + {{20, 299}, {80, 40}} + + NO + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + 1 + Join + Join + Join + Join + + 2 + MC4yOTQxMTc2NiAwLjM5NjA3ODQ3IDAuNTgwMzkyMTgAA + + + + + + 3 + MAA + + + + + -2147483355 + {{66, 0}, {189, 31}} + + NO + YES + NO + ( Shake to join any player ) + + TrebuchetMS-Bold + 1.400000e+01 + 16 + + + 1 + MSAxIDEAA + + + 1 + NO + 1.000000e+01 + 1 + + + + 269 + {{0, 347}, {320, 69}} + + NO + NO + 7 + NO + + NSImage + gamespyFooter.png + + + + + 268 + {{220, 299}, {80, 40}} + + NO + NO + 0 + 0 + + 1 + Host + Host + Host + Host + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + 269 + {{224, 29}, {90, 40}} + + NO + NO + 0 + 0 + + 1 + Refreshing + Refresh + Refreshing + Refresh + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + 292 + {{15, 32}, {201, 35}} + + NO + YES + NO + Games available: 0 + + TrebuchetMS-Bold + 2.000000e+01 + 16 + + + 1 + MSAxIDEAA + + 1 + + + + 1 + 1.000000e+01 + + + {320, 416} + + + YES + + + NO + + + + + + YES + + + view + + + + 10 + + + + joinButton + + + + 12 + + + + gamePicker + + + + 13 + + + + joinButtonClicked: + + + 7 + + 14 + + + + dataSource + + + + 15 + + + + delegate + + + + 16 + + + + hostButton + + + + 18 + + + + hostButtonClicked: + + + 7 + + 19 + + + + RefreshButton + + + + 21 + + + + refreshButtonClicked: + + + 7 + + 22 + + + + shakeLabel + + + + 23 + + + + listCountLabel + + + + 25 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 7 + + + + + 8 + + + + + 9 + + + + + 17 + + + + + 20 + + + Refresh Button + + + 24 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 17.IBPluginDependency + 20.IBPluginDependency + 24.IBPluginDependency + 4.IBPluginDependency + 5.IBPluginDependency + 6.IBPluginDependency + 7.IBPluginDependency + 8.IBPluginDependency + 9.IBPluginDependency + + + YES + MatchmakingController + UIResponder + {{451, 353}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 26 + + + + YES + + MatchmakingController + UIViewController + + YES + + YES + hostButtonClicked: + joinButtonClicked: + refreshButtonClicked: + + + YES + id + id + id + + + + YES + + YES + RefreshButton + gamePicker + hostButton + joinButton + listCountLabel + shakeLabel + + + YES + UIButton + UIPickerView + UIButton + UIButton + UILabel + UILabel + + + + IBProjectSource + Samples/TapRace/iphone/MatchmakingController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/menu.png b/Samples/TapRace/iphone/Resources/menu.png new file mode 100644 index 00000000..e2c224f8 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/menu.png differ diff --git a/Samples/TapRace/iphone/Resources/menu.xib b/Samples/TapRace/iphone/Resources/menu.xib new file mode 100644 index 00000000..43460631 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/menu.xib @@ -0,0 +1,830 @@ + + + + 528 + 9J61 + 677 + 949.46 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + Menu + + Menu + 1 + + + + + 274 + + YES + + + 292 + {{58, 25}, {203, 60}} + + + 3 + MCAwAA + + NO + NO + 2 + 0 + + TrebuchetMS-Bold + 1.600000e+01 + 16 + + 1 + Single player + Single player + Single player + Single Player + + 1 + MSAxIDEAA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MAA + + + + + 292 + {{58, 93}, {203, 60}} + + + NO + NO + 2 + 0 + + 1 + Two players + Two players + Two players + Two Players + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + 292 + {{58, 161}, {203, 60}} + + + NO + NO + 2 + 2 + 0 + + 1 + Leaderboards + Leaderboards + Leaderboards + Leaderboards + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + 269 + {{0, 345}, {320, 75}} + + NO + NO + 7 + NO + + NSImage + gamespyFooter.png + + + + + 265 + {{270, 372}, {18, 19}} + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + 3 + YES + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + + 292 + {{264, 315}, {50, 25}} + + + NO + YES + NO + 3.33 + + TrebuchetMS-Bold + 1.800000e+01 + 16 + + + 2 + MC4xOTYwNzg0NSAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{35, 315}, {229, 25}} + + + NO + YES + NO + sergsregt + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{78, 26}, {53, 57}} + + NO + 2 + NO + + NSImage + singlePlayer.png + + + + + 292 + {{70, 161}, {71, 55}} + + NO + 1 + NO + + NSImage + leaderboard.png + + + + + 292 + {{70, 96}, {70, 54}} + + NO + 1 + NO + + NSImage + buddies.png + + + + + 292 + {{5, 315}, {30, 25}} + + + NO + YES + NO + 5 + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{107, 293}, {104, 15}} + + + 1 + MCAwIDAAA + + YES + NO + Top 5 Tappers + + TrebuchetMS-Bold + 1.400000e+01 + 16 + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{0, 341}, {320, 5}} + + + 2 + MC40MzEzNzI1OCAwLjQzOTIxNTcyIDAuNDc0NTA5ODQAA + + NO + YES + NO + + + Helvetica-Bold + 1.400000e+01 + 16 + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{0, 309}, {320, 5}} + + + 2 + MC40MzEzNzI1OCAwLjQzOTIxNTcyIDAuNDc0NTA5ODQAA + + NO + YES + NO + + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{0, 345}, {320, 1}} + + + NO + YES + NO + + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{315, 314}, {4, 27}} + + + 2 + MC40MzEzNzI1OCAwLjQzOTIxNTcyIDAuNDc0NTA5ODQAA + + NO + YES + NO + + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{319, 310}, {1, 35}} + + + NO + YES + NO + + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{324, 297}, {1, 35}} + + + NO + YES + NO + + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{0, 314}, {5, 27}} + + + 2 + MC40MzEzNzI1OCAwLjQzOTIxNTcyIDAuNDc0NTA5ODQAA + + NO + YES + NO + + + + + 1 + 1.000000e+01 + 1 + + + {320, 416} + + + + + NO + + + + + + YES + + + view + + + + 9 + + + + multiPlayerButton + + + + 10 + + + + leaderboardsButton + + + + 11 + + + + singlePlayerButton + + + + 12 + + + + onLeaderboardsClicked: + + + 7 + + 14 + + + + onMultiPlayerClicked: + + + 7 + + 15 + + + + onSinglePlayerClicked: + + + 7 + + 16 + + + + showInfo + + + 7 + + 26 + + + + leadersLabel + + + + 30 + + + + leadersTimeLabel + + + + 31 + + + + leadersPositionLabel + + + + 39 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + + + + + + + + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 8 + + + + + 17 + + + YES + + + + + + 24 + + + + + 27 + + + + + 29 + + + + + 34 + + + + + 35 + + + + + 36 + + + + + 37 + + + + + 40 + + + + + 41 + + + + + 42 + + + + + 43 + + + + + 44 + + + + + 45 + + + + + 49 + + + + + 50 + + + + + 51 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 17.IBPluginDependency + 24.IBPluginDependency + 27.IBPluginDependency + 29.IBPluginDependency + 34.IBPluginDependency + 35.IBPluginDependency + 36.IBPluginDependency + 37.IBPluginDependency + 4.IBPluginDependency + 40.IBPluginDependency + 42.IBPluginDependency + 43.IBPluginDependency + 44.IBPluginDependency + 45.IBPluginDependency + 49.IBPluginDependency + 5.IBPluginDependency + 50.IBPluginDependency + 51.IBPluginDependency + 6.IBPluginDependency + 8.IBPluginDependency + + + YES + MenuController + UIResponder + {{745, 222}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 51 + + + + YES + + MenuController + UIViewController + + YES + + YES + onLeaderboardsClicked: + onMultiPlayerClicked: + onSinglePlayerClicked: + showAbout: + showInfo + + + YES + id + id + id + id + id + + + + YES + + YES + leaderboardsButton + leadersLabel + leadersPositionLabel + leadersTimeLabel + multiPlayerButton + singlePlayerButton + + + YES + UIButton + UILabel + UILabel + UILabel + UIButton + UIButton + + + + IBProjectSource + Samples/TapRace/iphone/MenuController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/nophoto.png b/Samples/TapRace/iphone/Resources/nophoto.png new file mode 100644 index 00000000..a8512e47 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/nophoto.png differ diff --git a/Samples/TapRace/iphone/Resources/people.png b/Samples/TapRace/iphone/Resources/people.png new file mode 100644 index 00000000..a3363991 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/people.png differ diff --git a/Samples/TapRace/iphone/Resources/playAgain.png b/Samples/TapRace/iphone/Resources/playAgain.png new file mode 100644 index 00000000..c64e92ea Binary files /dev/null and b/Samples/TapRace/iphone/Resources/playAgain.png differ diff --git a/Samples/TapRace/iphone/Resources/singlePlayer.png b/Samples/TapRace/iphone/Resources/singlePlayer.png new file mode 100644 index 00000000..95e9f7b5 Binary files /dev/null and b/Samples/TapRace/iphone/Resources/singlePlayer.png differ diff --git a/Samples/TapRace/iphone/Resources/singleplayergame.xib b/Samples/TapRace/iphone/Resources/singleplayergame.xib new file mode 100644 index 00000000..e555c9de --- /dev/null +++ b/Samples/TapRace/iphone/Resources/singleplayergame.xib @@ -0,0 +1,645 @@ + + + + 528 + 9J61 + 677 + 949.46 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + Single Player Game + + + + 274 + + YES + + + 290 + {{20, 86}, {280, 11}} + + + 1 + MSAwLjk0OTE4MDI1IDAuOTcxNjgwNzYAA + + NO + YES + NO + 1 + + + + 292 + {{85, 20}, {149, 45}} + + NO + YES + NO + 00:00.00 + + TrebuchetMS-Bold + 3.600000e+01 + 16 + + + 1 + MCAwIDAAA + + + 1 + 1.000000e+01 + 1 + + + + -2147483356 + {{35, 32}, {33, 21}} + + NO + YES + NO + Time: + + Helvetica + 3.600000e+01 + 16 + + + 3 + MQA + + 2 + + + + 1 + 1.000000e+01 + + + + 269 + {{60, 302}, {124, 23}} + + NO + YES + NO + Previous Best: + + TrebuchetMS-Bold + 1.800000e+01 + 16 + + + + 1 + 1.000000e+01 + + + + 269 + {{187, 302}, {72, 25}} + + NO + YES + NO + 00:00.00 + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{20, 20}, {57, 16}} + + NO + YES + NO + START + + TrebuchetMS-Bold + 1.400000e+01 + 16 + + + + 1 + NO + 1.000000e+01 + + + + 289 + {{251, 20}, {49, 16}} + + NO + YES + NO + FINISH + + + + 1 + NO + 1.000000e+01 + 2 + + + + 300 + {{20, 153}, {114, 111}} + + + 1 + MSAxIDEAA + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + NO + NO + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MAA + + + NSImage + btn_down.png + + + + + NSImage + btn_up.png + + + + + 300 + {{195, 153}, {105, 111}} + + + NO + NO + 0 + 0 + + NO + NO + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + + + + 269 + {{0, 343}, {320, 73}} + + NO + NO + 7 + NO + + NSImage + gamespyFooter.png + + + + + 292 + {{40, 116}, {239, 21}} + + + 3 + MCAwAA + + NO + YES + NO + Start tapping the keys to begin! + + + + 1 + 1.000000e+01 + 1 + + + + 265 + {{270, 371}, {18, 19}} + + NO + NO + 0 + 0 + + 3 + YES + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + {320, 416} + + + NO + + + NO + + + + + + YES + + + localProgress + + + + 15 + + + + tapButton1 + + + + 17 + + + + tapButton2 + + + + 18 + + + + view + + + + 19 + + + + tapButtonPressed: + + + 1 + + 21 + + + + startButtonClicked: + + + 1 + + 27 + + + + gameTimeLabel + + + + 29 + + + + previousBestLabel + + + + 30 + + + + previousBestTimeLabel + + + + 31 + + + + messageLabel + + + + 33 + + + + showInfo + + + 7 + + 35 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 4 + + + + + 5 + + + + + 7 + + + + + 8 + + + + + 9 + + + + + 10 + + + + + 11 + + + + + 12 + + + + + 14 + + + + + 22 + + + YES + + + + + 6 + + + + + 32 + + + + + 34 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 10.IBPluginDependency + 11.IBPluginDependency + 12.IBPluginDependency + 14.IBPluginDependency + 22.IBPluginDependency + 32.IBPluginDependency + 34.IBPluginDependency + 4.IBPluginDependency + 5.IBPluginDependency + 6.IBPluginDependency + 7.IBPluginDependency + 8.IBPluginDependency + 9.IBPluginDependency + + + YES + GameController + UIResponder + {{287, 295}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 35 + + + + YES + + GameController + UIViewController + + YES + + YES + showInfo + startButtonClicked: + tapButtonPressed: + + + YES + id + id + id + + + + YES + + YES + activityIndicator + gameTimeLabel + localPlayerLabel + localProgress + messageLabel + previousBestLabel + previousBestTimeLabel + remotePlayerLabel + remoteProgress + tapButton1 + tapButton2 + + + YES + UIActivityIndicatorView + UILabel + UILabel + UIProgressView + UILabel + UILabel + UILabel + UILabel + UIProgressView + UIButton + UIButton + + + + IBProjectSource + Samples/TapRace/iphone/GameController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/singleplayerresults.xib b/Samples/TapRace/iphone/Resources/singleplayerresults.xib new file mode 100644 index 00000000..359b3c42 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/singleplayerresults.xib @@ -0,0 +1,778 @@ + + + + 528 + 9J61 + 677 + 949.46 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + Results + + + + 274 + + YES + + + 292 + {{83, 20}, {153, 50}} + + NO + YES + NO + 00:00.00 + + TrebuchetMS-Bold + 3.600000e+01 + 16 + + + 1 + MSAxIDEAA + + + 1 + NO + 1.000000e+01 + 1 + + + + 269 + {{0, 343}, {320, 73}} + + NO + NO + 4 + NO + + NSImage + gamespyFooter.png + + + + + -2147483356 + {{73, 0}, {184, 27}} + + + 1 + MCAwIDAAA + + YES + NO + TmV3IEJlc3QgVGltZSF/A + + TrebuchetMS-Bold + 1.800000e+01 + 16 + + + + 1 + 1.600000e+01 + 2 + 1 + + + + 269 + {{210, 245}, {90, 90}} + + NO + 1 + 0 + 0 + + MarkerFelt-Thin + 1.800000e+01 + 16 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MAA + + + NSImage + leaderboard.png + + + + + 269 + {{20, 245}, {90, 90}} + + NO + 1 + 0 + 0 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + NSImage + singlePlayer.png + + + + + -2147483356 + {{20, 72}, {162, 21}} + + NO + YES + NO + Previous Best Time: + + + + 1 + 1.000000e+01 + + + + -2147483356 + {{190, 67}, {82, 30}} + + + NO + YES + NO + 00:00.00 + + + + 1 + 1.000000e+01 + + + + 265 + {{270, 370}, {18, 19}} + + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + 3 + YES + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + + 292 + {{113, 113}, {48, 19}} + + NO + YES + NO + Races: + + TrebuchetMS-Bold + 1.400000e+01 + 16 + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{170, 113}, {46, 19}} + + NO + YES + NO + 99999 + + + + 1 + 1.000000e+01 + + + + 292 + {{62, 165}, {99, 19}} + + NO + YES + NO + Average Time: + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{170, 165}, {64, 19}} + + NO + YES + NO + 00:00.00 + + + + 1 + 1.000000e+01 + + + + 292 + {{88, 138}, {73, 19}} + + NO + YES + NO + Best Time: + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{170, 138}, {66, 19}} + + NO + YES + NO + 00:00.00 + + + + 1 + 1.000000e+01 + + + + 292 + {{40, 192}, {121, 19}} + + NO + YES + NO + Average Taps/Sec.: + + + + 1 + 1.000000e+01 + 2 + + + + 292 + {{170, 192}, {41, 19}} + + NO + YES + NO + 1.0 + + + + 1 + 1.000000e+01 + + + {320, 416} + + + 1 + + + NO + + + + + + YES + + + view + + + + 23 + + + + leaderboardsButton + + + + 25 + + + + playAgainButton + + + + 28 + + + + leaderboardsButtonClicked: + + + 7 + + 31 + + + + playAgainButtonClicked: + + + 7 + + 33 + + + + gameTimeLabel + + + + 34 + + + + bestTimeLabel + + + + 57 + + + + averageTimeLabel + + + + 58 + + + + gamesPlayedLabel + + + + 59 + + + + congratsLabel + + + + 63 + + + + newbestTimeLabel + + + + 66 + + + + oldbestLabel + + + + 68 + + + + showInfo + + + 7 + + 70 + + + + avgTapsPerSecLabel + + + + 73 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + + + + + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 5 + + + + + 13 + + + + + 22 + + + + + 60 + + + Congrats Label + + + 19 + + + + + 20 + + + + + 67 + + + Old Best Label + + + 62 + + + NewBestTime + + + 69 + + + + + 51 + + + + + 52 + + + + + 55 + + + + + 56 + + + + + 53 + + + + + 54 + + + + + 71 + + + + + 72 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 13.IBPluginDependency + 19.IBPluginDependency + 20.IBPluginDependency + 22.IBPluginDependency + 5.IBPluginDependency + 51.IBPluginDependency + 52.IBPluginDependency + 53.IBPluginDependency + 54.IBPluginDependency + 55.IBPluginDependency + 56.IBPluginDependency + 60.IBPluginDependency + 62.IBPluginDependency + 67.IBPluginDependency + 69.IBPluginDependency + 71.IBPluginDependency + 72.IBPluginDependency + + + YES + GameResultsController + UIResponder + {{919, 171}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 73 + + + + YES + + GameResultsController + UIViewController + + YES + + YES + gotoMatchMaking + gotoMenu + leaderboardsButtonClicked: + menuButtonClicked: + playAgainButtonClicked: + showInfo + viewMyStatsClicked: + viewOpponentClicked: + + + YES + id + id + id + id + id + id + id + id + + + + YES + + YES + averageTimeLabel + avgTapsPerSecLabel + bestTimeLabel + busyLocal + busyRemote + congratsLabel + gameTimeLabel + gamesPlayedLabel + leaderboardsButton + localPlayerLabel + localThumbnailButton + localTimeLabel + localWinnerLabel + menuButton + newbestLabel + newbestTimeLabel + oldbestLabel + playAgainButton + remotePlayerLabel + remoteThumbnailButton + remoteTimeLabel + remoteWinnerLabel + tieLabel + + + YES + UILabel + UILabel + UILabel + UIActivityIndicatorView + UIActivityIndicatorView + UILabel + UILabel + UILabel + UIButton + UILabel + UIButton + UILabel + UILabel + UIButton + UILabel + UILabel + UILabel + UIButton + UILabel + UIButton + UILabel + UILabel + UILabel + + + + IBProjectSource + Samples/TapRace/iphone/GameResultsController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/takephoto.xib b/Samples/TapRace/iphone/Resources/takephoto.xib new file mode 100644 index 00000000..d268c179 --- /dev/null +++ b/Samples/TapRace/iphone/Resources/takephoto.xib @@ -0,0 +1,167 @@ + + + + 528 + 9F33 + 672 + 949.34 + 352.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + IBFilesOwner + + + IBFirstResponder + + + + 292 + + YES + + + 274 + {{0, 62}, {320, 418}} + + NO + NO + 4 + NO + + NSImage + ScreenshotPictures.png + + + + + 292 + {{121, 20}, {77, 21}} + + NO + YES + NO + Get Photo + + 1 + MCAwIDAAA + + + 1 + 1.000000e+01 + + + {320, 480} + + + 3 + MQA + + 2 + + + + + + + YES + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 6 + + + + + 7 + + + + + + + YES + + YES + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 6.IBPluginDependency + 7.IBPluginDependency + + + YES + UIResponder + {{159, 384}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 7 + + + 0 + ../../Gamespy.xcodeproj + 3 + + diff --git a/Samples/TapRace/iphone/Resources/tapImage.png b/Samples/TapRace/iphone/Resources/tapImage.png new file mode 100644 index 00000000..cfa98f0c Binary files /dev/null and b/Samples/TapRace/iphone/Resources/tapImage.png differ diff --git a/Samples/TapRace/iphone/Resources/trophy.png b/Samples/TapRace/iphone/Resources/trophy.png new file mode 100644 index 00000000..1221e0de Binary files /dev/null and b/Samples/TapRace/iphone/Resources/trophy.png differ diff --git a/Samples/TapRace/iphone/Resources/twoplayergame.xib b/Samples/TapRace/iphone/Resources/twoplayergame.xib new file mode 100644 index 00000000..109f7ebe --- /dev/null +++ b/Samples/TapRace/iphone/Resources/twoplayergame.xib @@ -0,0 +1,635 @@ + + + + 528 + 9J61 + 677 + 949.46 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + Two Player Game + + + + 274 + + YES + + + 290 + {{20, 83}, {280, 11}} + + NO + YES + YES + 1 + + + + 292 + {{90, 11}, {147, 35}} + + NO + YES + NO + 00:00.00 + + Helvetica + 3.600000e+01 + 16 + + + 1 + MCAwIDAAA + + + 1 + 1.000000e+01 + 2 + + + + -2147483356 + {{49, 18}, {33, 21}} + + NO + YES + NO + Time: + + Helvetica + 1.300000e+01 + 16 + + + + 1 + 1.000000e+01 + + + + 292 + {{18, 101}, {36, 16}} + + NO + YES + NO + START + + Helvetica + 8.000000e+00 + 16 + + + + 1 + NO + 1.000000e+01 + + + + 289 + {{264, 101}, {36, 16}} + + NO + YES + NO + FINISH + + + + 1 + NO + 1.000000e+01 + 2 + + + + 293 + {{20, 175}, {105, 111}} + + + 1 + MSAxIDEAA + + NO + NO + NO + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MAA + + + NSImage + btn_down.png + + + + + NSImage + btn_up.png + + + + + 293 + {{195, 175}, {105, 111}} + + NO + NO + NO + 0 + 0 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + + + + + 269 + {{0, 345}, {320, 73}} + + NO + NO + 7 + NO + + NSImage + gamespyFooter.png + + + + + 290 + {{18, 125}, {280, 11}} + + + NO + YES + YES + 1 + + + + 292 + {{145, 54}, {36, 21}} + + NO + YES + NO + You + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{130, 143}, {66, 21}} + + NO + YES + NO + Opponent + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{62, 294}, {203, 21}} + + NO + YES + NO + Waiting for other player... + + + 1 + 1.000000e+01 + + + + 292 + {{34, 295}, {20, 20}} + + NO + NO + NO + YES + 2 + + + {320, 416} + + + + + NO + + + + + + YES + + + view + + + + 19 + + + + localProgress + + + + 20 + + + + tapButton1 + + + + 22 + + + + tapButton2 + + + + 23 + + + + tapButtonPressed: + + + 1 + + 24 + + + + tapButtonPressed: + + + 1 + + 26 + + + + remoteProgress + + + + 27 + + + + activityIndicator + + + + 28 + + + + gameTimeLabel + + + + 31 + + + + messageLabel + + + + 32 + + + + localPlayerLabel + + + + 33 + + + + remotePlayerLabel + + + + 34 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 4 + + + + + 5 + + + + + 6 + + + + + 9 + + + + + 10 + + + + + 11 + + + + + 12 + + + + + 13 + + + + + 14 + + + + + 15 + + + + + 16 + + + + + 17 + + + + + 18 + + + + + 29 + + + YES + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 10.IBPluginDependency + 11.IBPluginDependency + 12.IBPluginDependency + 13.IBPluginDependency + 14.IBPluginDependency + 15.IBPluginDependency + 16.IBPluginDependency + 17.IBPluginDependency + 18.IBPluginDependency + 29.IBPluginDependency + 4.IBPluginDependency + 5.IBPluginDependency + 6.IBPluginDependency + 9.IBPluginDependency + + + YES + GameController + UIResponder + {{968, 200}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 36 + + + + YES + + GameController + UIViewController + + YES + + YES + showInfo + startButtonClicked: + tapButtonPressed: + + + YES + id + id + id + + + + YES + + YES + activityIndicator + gameTimeLabel + localPlayerLabel + localProgress + messageLabel + previousBestLabel + previousBestTimeLabel + remotePlayerLabel + remoteProgress + tapButton1 + tapButton2 + + + YES + UIActivityIndicatorView + UILabel + UILabel + UIProgressView + UILabel + UILabel + UILabel + UILabel + UIProgressView + UIButton + UIButton + + + + IBProjectSource + Samples/TapRace/iphone/GameController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/Resources/twoplayerresults.xib b/Samples/TapRace/iphone/Resources/twoplayerresults.xib new file mode 100644 index 00000000..5251736c --- /dev/null +++ b/Samples/TapRace/iphone/Resources/twoplayerresults.xib @@ -0,0 +1,841 @@ + + + + 528 + 9L31a + 677 + 949.54 + 353.00 + + YES + + + + YES + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + YES + + YES + + + YES + + + + YES + + IBFilesOwner + + + IBFirstResponder + + + Results + + + + 274 + + YES + + + 269 + {{0, 343}, {320, 73}} + + NO + NO + 7 + NO + + NSImage + gamespyFooter.png + + + + + 290 + {{19, 145}, {120, 29}} + + NO + YES + NO + you + + TrebuchetMS-Bold + 1.400000e+01 + 16 + + + 1 + MSAxIDEAA + + + 1 + 1.000000e+01 + 1 + + + + -2147483358 + {{34, 17}, {90, 29}} + + NO + YES + NO + Winner + + TrebuchetMS-Bold + 2.400000e+01 + 16 + + + 2 + MC4yMDc4NDMxNSAwLjMxNzY0NzA3IDAuNTI1NDkwMjIAA + + + 1 + MSAwLjUgMCAwAA + + + 1 + MSAxIDEgMAA + + 1 + 1.000000e+01 + 1 + + + + -2147483358 + {{139, 16}, {42, 30}} + + NO + YES + NO + Tie + + + 2 + MC4yMDc4NDMxNSAwLjMxNzY0NzA3IDAuNTI1NDkwMjIAA + + + 1 + MSAwLjUgMCAwAA + + + 1 + MSAxIDEgMAA + + 1 + 1.000000e+01 + 1 + + + + -2147483358 + {{201, 17}, {90, 29}} + + NO + YES + NO + Winner + + + 2 + MC4yMDc4NDMxNSAwLjMxNzY0NzA3IDAuNTI1NDkwMjIAA + + + + 1 + MSAxIDEgMAA + + 1 + 1.000000e+01 + 1 + + + + 290 + {{187, 145}, {120, 29}} + + NO + YES + NO + opponent + + + + 1 + 1.000000e+01 + 1 + + + + 268 + {{49, 245}, {90, 90}} + + NO + 1 + 0 + 0 + + Helvetica-Bold + 1.500000e+01 + 16 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MAA + + + NSImage + buddies_rematch.png + + + + + 269 + {{187, 245}, {90, 90}} + + NO + 1 + 0 + 0 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + NSImage + leaderboard.png + + + + + 265 + {{270, 370}, {18, 19}} + + NO + NO + 0 + 0 + + 3 + YES + + 3 + MQA + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + 3 + MC41AA + + + + + 292 + {{26, 182}, {105, 30}} + + NO + YES + NO + 00:00.00 + + TrebuchetMS-Bold + 2.500000e+01 + 16 + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{195, 182}, {105, 30}} + + NO + YES + NO + 00:00.00 + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{148, 88}, {23, 21}} + + NO + YES + NO + vs + + 1 + MSAxIDEAA + + 1 + + + + 1 + 1.000000e+01 + 1 + + + + 292 + {{209, 53}, {75, 93}} + + NO + 1 + NO + 0 + 0 + + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + NSImage + singlePlayer.png + + + + + 292 + {{41, 53}, {75, 93}} + + + 1 + MCAwIDAgMAA + + NO + 1 + 0 + 0 + + 1 + + + 1 + MC4xOTYwNzg0MyAwLjMwOTgwMzkzIDAuNTIxNTY4NjYAA + + + + + + + 292 + {{229, 93}, {37, 37}} + + NO + NO + NO + NO + 0 + + + + 292 + {{60, 93}, {37, 37}} + + NO + NO + NO + NO + 0 + + + {320, 416} + + + 1 + MCAwIDAAA + + + + NO + + + + + + YES + + + view + + + + 25 + + + + leaderboardsButton + + + + 27 + + + + playAgainButton + + + + 28 + + + + leaderboardsButtonClicked: + + + 7 + + 32 + + + + playAgainButtonClicked: + + + 7 + + 34 + + + + localTimeLabel + + + + 39 + + + + remoteTimeLabel + + + + 40 + + + + showInfo + + + 7 + + 45 + + + + localPlayerLabel + + + + 53 + + + + remotePlayerLabel + + + + 55 + + + + localWinnerLabel + + + + 61 + + + + remoteWinnerLabel + + + + 62 + + + + tieLabel + + + + 63 + + + + localThumbnailButton + + + + 68 + + + + remoteThumbnailButton + + + + 69 + + + + viewMyStatsClicked: + + + 7 + + 70 + + + + busyRemote + + + + 72 + + + + busyLocal + + + + 74 + + + + + YES + + 0 + + YES + + + + + + 1 + + + YES + + + + + + + + + + + + + + + + + + + + + -1 + + + RmlsZSdzIE93bmVyA + + + -2 + + + + + 11 + + + + + 24 + + + + + 35 + + + + + 14 + + + + + 13 + + + + + 44 + + + + + 16 + + + + + 22 + + + + + 46 + + + + + 47 + + + + + 48 + + + + + 54 + + + + + 56 + + + + + 66 + + + + + 67 + + + + + 71 + + + + + 73 + + + + + + + YES + + YES + -1.CustomClassName + -2.CustomClassName + 1.IBEditorWindowLastContentRect + 1.IBPluginDependency + 11.IBPluginDependency + 13.IBPluginDependency + 14.IBPluginDependency + 16.IBPluginDependency + 22.IBPluginDependency + 24.IBPluginDependency + 35.IBPluginDependency + 44.IBPluginDependency + 46.IBPluginDependency + 47.IBPluginDependency + 48.IBPluginDependency + 54.IBPluginDependency + 56.IBPluginDependency + 66.IBPluginDependency + 67.IBPluginDependency + 71.IBPluginDependency + 73.IBPluginDependency + + + YES + GameResultsController + UIResponder + {{737, 100}, {320, 480}} + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + com.apple.InterfaceBuilder.IBCocoaTouchPlugin + + + + YES + + YES + + + YES + + + + + YES + + YES + + + YES + + + + 74 + + + + YES + + GameResultsController + UIViewController + + YES + + YES + gotoMatchMaking + gotoMenu + leaderboardsButtonClicked: + menuButtonClicked: + playAgainButtonClicked: + showInfo + viewMyStatsClicked: + viewOpponentClicked: + + + YES + id + id + id + id + id + id + id + id + + + + YES + + YES + averageTimeLabel + avgTapsPerSecLabel + bestTimeLabel + busyLocal + busyRemote + congratsLabel + gameTimeLabel + gamesPlayedLabel + leaderboardsButton + localPlayerLabel + localThumbnailButton + localTimeLabel + localWinnerLabel + menuButton + newbestLabel + newbestTimeLabel + oldbestLabel + playAgainButton + remotePlayerLabel + remoteThumbnailButton + remoteTimeLabel + remoteWinnerLabel + tieLabel + + + YES + UILabel + UILabel + UILabel + UIActivityIndicatorView + UIActivityIndicatorView + UILabel + UILabel + UILabel + UIButton + UILabel + UIButton + UILabel + UILabel + UIButton + UILabel + UILabel + UILabel + UIButton + UILabel + UIButton + UILabel + UILabel + UILabel + + + + IBProjectSource + Samples/TapRace/iphone/GameResultsController.h + + + + + 0 + ../../../../Gamespy.xcodeproj + 3 + 3.0 + + diff --git a/Samples/TapRace/iphone/TapRace-Info.plist b/Samples/TapRace/iphone/TapRace-Info.plist new file mode 100644 index 00000000..8166aa95 --- /dev/null +++ b/Samples/TapRace/iphone/TapRace-Info.plist @@ -0,0 +1,26 @@ + + + + + CFBundleDevelopmentRegion + English + CFBundleIconFile + icon_57x57.png + CFBundleExecutable + ${EXECUTABLE_NAME} + CFBundleIdentifier + com.fox.gamespy.${PRODUCT_NAME:identifier} + CFBundleInfoDictionaryVersion + 6.0 + CFBundlePackageType + APPL + CFBundleSignature + ???? + CFBundleVersion + 1.2 + NSMainNibFile + MainWindow + UIRequiresPersistentWiFi + + + diff --git a/Samples/TapRace/iphone/UserStatsController.h b/Samples/TapRace/iphone/UserStatsController.h new file mode 100644 index 00000000..575a3c16 --- /dev/null +++ b/Samples/TapRace/iphone/UserStatsController.h @@ -0,0 +1,55 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import + + +@interface UserStatsController : UIViewController +{ + IBOutlet UIImageView* thumbNail; + IBOutlet UILabel* userName; + IBOutlet UILabel* userTotalRaces; + IBOutlet UILabel* userTotalWins; + IBOutlet UILabel* userTotalLosses; + IBOutlet UILabel* userRatio; + IBOutlet UILabel* userBestTime; + IBOutlet UILabel* userWinStreak; + IBOutlet UILabel* myRank; + + IBOutlet UILabel* myTotalRaces; + IBOutlet UILabel* myTotalWins; + IBOutlet UILabel* myTotalLosses; + IBOutlet UILabel* myRatio; + IBOutlet UILabel* myBestTime; + IBOutlet UILabel* myWinStreak; + + IBOutlet UIButton* makeBuddyButton; + IBOutlet UIButton* reportImageButton; + + UIAlertView* alertView; + int currentAlert; + NSTimer* updateTimer; +} +@property (nonatomic, retain) UIImageView* thumbNail; +@property (nonatomic, retain) UILabel* userName; +@property (nonatomic, retain) UILabel* userTotalRaces; +@property (nonatomic, retain) UILabel* userTotalWins; +@property (nonatomic, retain) UILabel* userTotalLosses; +@property (nonatomic, retain) UILabel* userRatio; +@property (nonatomic, retain) UILabel* userBestTime; +@property (nonatomic, retain) UILabel* userWinStreak; +@property (nonatomic, retain) UILabel* myRank; + +@property (nonatomic, retain) UILabel* myTotalRaces; +@property (nonatomic, retain) UILabel* myTotalWins; +@property (nonatomic, retain) UILabel* myTotalLosses; +@property (nonatomic, retain) UILabel* myRatio; +@property (nonatomic, retain) UILabel* myBestTime; +@property (nonatomic, retain) UILabel* myWinStreak; + +- (IBAction)makeBuddyButtonClicked: (id)sender; +- (IBAction)reportImageButtonClicked: (id)sender; +- (IBAction)showInfo; + + +@end diff --git a/Samples/TapRace/iphone/UserStatsController.mm b/Samples/TapRace/iphone/UserStatsController.mm new file mode 100644 index 00000000..937b36d0 --- /dev/null +++ b/Samples/TapRace/iphone/UserStatsController.mm @@ -0,0 +1,284 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "UserStatsController.h" +#import "LeadersController.h" +#import "BuddyListCellView.h" +#import "AppDelegate.h" +#import "Utility.h" +#import "LoginController.h" + +enum AppAlert +{ + ALERT_NONE, + ALERT_MAKEBUDDY, + ALERT_REPORTIMAGE +}; + +@implementation UserStatsController + +@synthesize thumbNail; +@synthesize userName; +@synthesize userTotalRaces; +@synthesize userTotalWins; +@synthesize userTotalLosses; +@synthesize userRatio; +@synthesize userBestTime; +@synthesize userWinStreak; + +@synthesize myRank; +@synthesize myTotalRaces; +@synthesize myTotalWins; +@synthesize myTotalLosses; +@synthesize myRatio; +@synthesize myBestTime; +@synthesize myWinStreak; + +- (void)dealloc +{ + if(thumbNail != nil) [thumbNail release]; + [userName release]; + [userTotalRaces release]; + [userTotalWins release]; + [userTotalLosses release]; + [userRatio release]; + [userBestTime release]; + [userWinStreak release]; + [myRank release]; + [myTotalRaces release]; + [myTotalWins release]; + [myTotalLosses release]; + [myRatio release]; + [myBestTime release]; + [myWinStreak release]; + //if(updateTimer != nil) [updateTimer invalidate]; + //[updateTimer release]; + [super dealloc]; +} + +// display image if one is received +//************************************* +- (void)updateTime: (NSTimer*)timer; +{ + LeaderboardObject* leader = [gLeaderboardData objectAtIndex: gLeaderboardProfileIndex]; + UIImage * img = leader.thumbNail; + + // Hide "Report image" button if no image or it is blocked or it is myself + if (img == nil || [appDelegate imageIsBlocked: leader.pictureFileId] || gPlayerData.profileId == leader.profileId ) + { + reportImageButton.hidden = YES; + [updateTimer invalidate]; + } + else + // if image found and it is not blocked, display it + // don't change if no image, will display the default No Photo + { + reportImageButton.hidden = NO; + [self.thumbNail setImage: img]; + [updateTimer invalidate]; + [self.thumbNail setNeedsDisplay]; + } + +} + + +// called after a delay to show rank +// gives the mainloop a chance for callback to set server value for rank +- (void)delayedRank +{ + LeaderboardObject* leader = (LeaderboardObject*)[gLeaderboardData objectAtIndex: gLeaderboardProfileIndex]; + + if( leader.rank != 0 ) + myRank.text = [NSString stringWithFormat: @"#%d", leader.rank ]; +} + + +//************************************* +- (void)viewWillAppear: (BOOL) animated +{ + int totalRaces; + int totalWins; + int bestTime; + + // V.1 hide buddy button + makeBuddyButton.hidden = YES; + + + + //[super viewWillAppear: animated]; + + LeaderboardObject* leader = (LeaderboardObject*)[gLeaderboardData objectAtIndex: gLeaderboardProfileIndex]; + + // if this is a buddy list, then hide the 'make buddy' button since he's already a buddy + if (gLeaderboardType == LEADERBOARD_BY_BUDDIES) + { + makeBuddyButton.hidden = YES; + } + + myRank.text = [NSString stringWithFormat: @"#%d", gLeaderboardProfileIndex + 1 ]; +/* else + { + // request rank from server + [appDelegate getMyLeaderPosition: &(leader.rank) ]; + + // go get it in 1 second + [self performSelector:@selector(delayedRank) withObject:nil afterDelay:1.0f]; + } +*/ + NSString* aTitle = [NSString stringWithFormat: @"%@'s Profile", leader.name]; + self.title = aTitle; //[leader.name copy]; // index of currently displayed profile + //self.title = [leader.name copy]; + // display image if not blocked and a picture exists + // if no image exists, will display the default No Photo + + UIImage * img = leader.thumbNail; + if( img == nil && leader.pictureFileId == 0 ) + { + // kick off a timer to poll for the image...just in case the leaderboard fills it in for us while we're sitting here + if (updateTimer != nil) { + [updateTimer invalidate]; + } + updateTimer = [NSTimer scheduledTimerWithTimeInterval: 0.1 target: self selector: @selector(updateTime:) userInfo: nil repeats: YES]; + } + else if ( ! [appDelegate imageIsBlocked: leader.pictureFileId] ) + { + reportImageButton.hidden = NO; + [self.thumbNail setImage: img]; + } + else // picture is blocked + { + reportImageButton.hidden = YES; + } + + if( gPlayerData.profileId == leader.profileId ) + { + reportImageButton.hidden = YES; + } + + self.userName.text = [leader.name copy]; + + totalRaces = leader.totalMatches; + self.userTotalRaces.text = [NSString stringWithFormat: @"%d", totalRaces]; + + totalWins = leader.careerWins; + self.userTotalWins.text = [NSString stringWithFormat: @"%d", totalWins]; + + self.userTotalLosses.text = [NSString stringWithFormat: @"%d", leader.careerLosses]; + if (totalWins > 0) { + self.userRatio.text = [NSString stringWithFormat: @"%d\%%", (totalWins*100)/totalRaces]; + } else { + self.userRatio.text = [NSString stringWithString: @"0%"]; + } + + bestTime = leader.bestRaceTime; + if ((bestTime <= 0) || (bestTime >= INITIAL_BEST_TIME)) + { + self.userBestTime.text = [NSString stringWithString: @"none"]; + } + else + { + SetTimeLabel(self.userBestTime, bestTime); + } + + self.userWinStreak.text = [NSString stringWithFormat: @"%d", leader.currentWinStreak]; + + self.myTotalRaces.text = [NSString stringWithFormat: @"%d", getLocalIntegerStat(matchTotalCompleteMatchesKey)]; + self.myTotalWins.text = [NSString stringWithFormat: @"%d", getLocalIntegerStat(matchWonKey)]; + self.myTotalLosses.text = [NSString stringWithFormat: @"%d", getLocalIntegerStat(matchLossKey)]; + + if (gPlayerData.totalMatches > 0) + { + self.myRatio.text = [NSString stringWithFormat: @"%d\%%", (getLocalIntegerStat(matchWonKey)*100) / getLocalIntegerStat(matchTotalCompleteMatchesKey)]; + } + else + { + self.myRatio.text = [NSString stringWithString: @"0%"]; + } + + int myBestRaceTime = getLocalIntegerStat(matchBestTimeKey); + if( (myBestRaceTime <= 0) || (myBestRaceTime >= INITIAL_BEST_TIME) ) + { + self.myBestTime.text = [NSString stringWithString: @"none"]; + } + else + { + SetTimeLabel(self.myBestTime, myBestRaceTime); + } + + self.myWinStreak.text = [NSString stringWithFormat: @"%d", gPlayerData.matchWinStreak]; + +} + +//************************************* +- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation +{ + // Return YES for supported orientations + return interfaceOrientation == UIInterfaceOrientationPortrait; +} + + +// Display the About page +- (IBAction)showInfo +{ + [ appDelegate showAboutInfo: self ]; +} + + +//************************************* +- (void)alertView: (UIAlertView*)alert didDismissWithButtonIndex: (NSInteger)buttonIndex +{ + switch (currentAlert) { + case ALERT_MAKEBUDDY: + if (buttonIndex != alertView.cancelButtonIndex) { + gsi_char reason[GP_REASON_LEN]; + reason[0] = 0; // null in any language + + LoginController* loginController = (LoginController*)[appDelegate findExistingControllerOfType: [LoginController class]]; + gpSendBuddyRequest( [loginController getGPConnectionPtr], + ((LeaderboardObject*)[gLeaderboardData objectAtIndex: gLeaderboardProfileIndex]).profileId, + reason ); + // this could cause a gpRecvBuddyRequest callback indicating that it was accepted or rejected. + // for this simple app, we don't care + } + break; + case ALERT_REPORTIMAGE: + // Block image if cancel button ("No") was not pressed + if (buttonIndex != alertView.cancelButtonIndex) + { + // set & display default image in user stat page + NSString* defaultImage = [[NSBundle mainBundle] pathForResource:@"singlePlayer" ofType:@"png"]; + UIImage* imageObj = [[UIImage alloc] initWithContentsOfFile:defaultImage]; + [self.thumbNail setImage: imageObj]; + + // add to block images list + LeaderboardObject* leader = (LeaderboardObject*)[gLeaderboardData objectAtIndex: gLeaderboardProfileIndex]; + NSNumber* aFileId = [[ NSNumber alloc] initWithUnsignedInteger: leader.pictureFileId ]; + [ gPlayerData.blockedImagesData addObject:aFileId ]; + + // save to file + [ gPlayerData.blockedImagesData writeToFile: [gPlayerData.playerDataPath stringByAppendingPathComponent: blockedImagesDataFile] atomically: YES]; + } + break; + } + currentAlert = ALERT_NONE; +} + + +//************************************* +- (IBAction)makeBuddyButtonClicked: (id)sender +{ + alertView = [[UIAlertView alloc] initWithTitle: @"Make Buddy" message: @"Are you sure?" delegate: self cancelButtonTitle: @"No" otherButtonTitles: @"Yes", nil]; + [alertView show]; + currentAlert = ALERT_MAKEBUDDY; +} + + +//************************************* +- (IBAction)reportImageButtonClicked: (id)sender +{ + alertView = [[UIAlertView alloc] initWithTitle: @"Report Image" message: @"Are you sure?" delegate: self cancelButtonTitle: @"No" otherButtonTitles: @"Yes", nil]; + [alertView show]; + currentAlert = ALERT_REPORTIMAGE; +} + +@end diff --git a/Samples/TapRace/iphone/Utility.h b/Samples/TapRace/iphone/Utility.h new file mode 100644 index 00000000..7dedc0e3 --- /dev/null +++ b/Samples/TapRace/iphone/Utility.h @@ -0,0 +1,72 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#ifndef UTILITY_INCLUDED + +// Allows expanding macros into string constants. +#define STRINGIZE1(x) #x +#define STRINGIZE(x) STRINGIZE1(x) + +#define getLocalIntegerStat(key) [(NSNumber*)[gPlayerData.playerStatsData objectForKey: key ] intValue] +#define getLocalFloatStat(key) [(NSNumber*)[gPlayerData.playerStatsData objectForKey: key ] floatValue] +#define getLocalStringStat(key) [(NSString*)[gPlayerData.playerStatsData objectForKey: key ]] + + +// Multipliers for conversion between device time units and real time units. +extern double absolute_to_millis; +extern double millis_to_absolute; +extern double absolute_to_seconds; +extern double seconds_to_absolute; + +// printf-style format string for displaying game time. +extern NSString* timeFormatString; + +// Generic function for setting the text of a time label. +void SetTimeLabel(UILabel* label, int timeInMilliseconds); + +// File name for storing single-player data. +extern NSString* playerStatsDataFile; +extern NSString* blockedImagesDataFile; + +// Keys for accessing single-player data. +extern NSString* singlePlayerBestTimeKey; +extern NSString* singlePlayerWorstTimeKey; +extern NSString* singlePlayerAverageTimeKey; +extern NSString* singlePlayerGamesPlayedKey; +extern NSString* singlePlayerTotalPlaysKey; +extern NSString* singlePlayerTotalRaceTimeKey; + + + +// Keys for accessing two-player game stats data. +extern NSString* matchBestTimeKey; +extern NSString* matchWorstTimeKey; +extern NSString* matchAverageTimeKey; +extern NSString* matchGamesPlayedKey; +extern NSString* matchWonKey; +extern NSString* matchLossKey; +extern NSString* matchWinStreakKey; +extern NSString* matchLossStreakKey; +extern NSString* matchTotalRaceTimeKey; +extern NSString* matchCareerDisconnectsKey; +extern NSString* matchDisconnectRateKey; +extern NSString* matchDrawsKey; +extern NSString* matchDrawStreakKey; + +extern NSString* matchCareerLongestWinStreakKey; +extern NSString* matchCareerLongestLossStreakKey; +extern NSString* matchCareerLongestDrawStreakKey; +extern NSString* matchTotalCompleteMatchesKey; + +// Key for timestamp of stored stats data +extern NSString* lastTimePlayedKey; + +// Debugging and user notification convenience functions. +void MessageBox(NSString* text, NSString* caption = nil); +void MessageBoxNoBlock(NSString* text, NSString* caption = nil, NSString* cancelButtonString = @"OK"); +void OutputDebugString(NSString* text); + +// Get the bold version of an existing font. Returns the existing font if there is no bold verson. +UIFont* GetBoldVersion(UIFont* font); + +#endif \ No newline at end of file diff --git a/Samples/TapRace/iphone/Utility.mm b/Samples/TapRace/iphone/Utility.mm new file mode 100644 index 00000000..00e02570 --- /dev/null +++ b/Samples/TapRace/iphone/Utility.mm @@ -0,0 +1,165 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import "Utility.h" +#import + +static mach_timebase_info_data_t InitTimebaseInfo(); +static mach_timebase_info_data_t timebase_info = InitTimebaseInfo(); + +double absolute_to_millis; +double millis_to_absolute; +double absolute_to_seconds; +double seconds_to_absolute; + +NSString* timeFormatString = @"%02u:%05.2f"; +NSString* playerStatsDataFile = @"playerStats.plist"; +NSString* blockedImagesDataFile = @"blockedImages.plist"; + +NSString* singlePlayerBestTimeKey = @"spBestTime"; +NSString* singlePlayerWorstTimeKey = @"spWorstTime"; +NSString* singlePlayerAverageTimeKey = @"spAverageTime"; +NSString* singlePlayerGamesPlayedKey = @"spGamesPlayed"; +NSString* singlePlayerTotalPlaysKey = @"spTotalPlays"; +NSString* singlePlayerTotalRaceTimeKey = @"spTotalRaceTime"; + +NSString* matchBestTimeKey = @"BestTime"; +NSString* matchWorstTimeKey = @"WorstTime"; +NSString* matchAverageTimeKey = @"AverageTime"; +NSString* matchGamesPlayedKey = @"GamesPlayed"; + +NSString* matchWonKey = @"Won"; +NSString* matchLossKey = @"Loss"; +NSString* matchDrawsKey = @"careerDraws"; + +NSString* matchWinStreakKey = @"WinStreak"; +NSString* matchLossStreakKey = @"currentLossStreak"; +NSString* matchDrawStreakKey = @"currentDrawStreak"; + +NSString* matchCareerLongestWinStreakKey = @"careerLongestWinStreak"; +NSString* matchCareerLongestLossStreakKey = @"careerLongestLossStreak"; +NSString* matchCareerLongestDrawStreakKey = @"careerLongestDrawStreak"; + +NSString* matchTotalRaceTimeKey = @"totalRaceTimeKeyotalRaceTime"; +NSString* matchCareerDisconnectsKey = @"careerDisconnects"; +NSString* matchDisconnectRateKey = @"disconnectRate"; +NSString* matchTotalCompleteMatchesKey = @"totalCompleteMatches"; + +NSString* lastTimePlayedKey = @"LastTimePlayed"; + + +void SetTimeLabel(UILabel* label, int timeInMilliseconds) +{ + label.text = [NSString stringWithFormat: timeFormatString, timeInMilliseconds / (60 * 1000), (timeInMilliseconds % (60 * 1000)) / 1000.0f]; +} + + +@interface MessageBoxDelegate : NSObject +{ + NSInteger clickedButtonIndex; +} + +@property(nonatomic, readonly) NSInteger clickedButtonIndex; + +@end + + +@implementation MessageBoxDelegate + +@synthesize clickedButtonIndex; + +- (id)init +{ + self = [super init]; + + if (self != nil) { + clickedButtonIndex = -1; + } + + return self; +} + +- (void)alertView: (UIAlertView*)alertView willDismissWithButtonIndex: (NSInteger)buttonIndex +{ + clickedButtonIndex = buttonIndex; +} + +@end + + + +void MessageBox(NSString* text, NSString* title) +{ + MessageBoxDelegate* delegate = [[MessageBoxDelegate alloc] init]; + UIAlertView* alertView = [[UIAlertView alloc] initWithTitle: title message: text delegate: delegate cancelButtonTitle: @"OK" otherButtonTitles: nil]; + [alertView show]; + + while (delegate.clickedButtonIndex == -1) { + [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode beforeDate: [NSDate dateWithTimeIntervalSinceNow: 0.10]]; + } + + [alertView release]; + [delegate release]; +} + + +void MessageBoxNoBlock(NSString* text, NSString* caption, NSString* cancelButtonString ) +{ + UIAlertView* alertView = [[UIAlertView alloc] initWithTitle: caption message: text delegate: nil cancelButtonTitle: cancelButtonString otherButtonTitles: nil]; + [alertView show]; + [alertView release]; +} + +void OutputDebugString(NSString* text) +{ +#ifdef GSI_COMMON_DEBUG + NSLog(@"%s\n", [text cStringUsingEncoding: NSASCIIStringEncoding]); +#endif +} + +UIFont* GetBoldVersion(UIFont* font) +{ + // Apple doesn't make this easy. There's no programmatic way to request the bold version of a given font, + // so we have to use the font names as a guide. + NSString* fontFamilyName = font.familyName; + NSArray* fontNames = [UIFont fontNamesForFamilyName: fontFamilyName]; + NSString* boldFontName = font.fontName; + NSUInteger shortestAttributeStringLength = NSUIntegerMax; + + // For each font name in the font's family, search for the string "bold." After this loop, boldFontName + // holds the best candidate. + for (NSString* fontName in fontNames) { + // Font names tend to follow the format "FamilyName-Attributes". We don't want to search the family + // name part (if there's a font family named "Kobold", don't match the "bold" part of that), so try + // to isolate just the attributes... + NSRange familyNameRange = [fontName rangeOfString: fontFamilyName options: NSCaseInsensitiveSearch]; + NSString* fontAttributes = [NSString stringWithFormat: @"%@ %@", [fontName substringToIndex: familyNameRange.location], [fontName substringFromIndex: familyNameRange.location + familyNameRange.length]]; + fontAttributes = [fontAttributes stringByTrimmingCharactersInSet: [NSCharacterSet characterSetWithCharactersInString: @" -"]]; + bool bold = [fontAttributes rangeOfString: @"bold" options: NSCaseInsensitiveSearch].length > 0; + + // A font can have more than one attribute, such as "ObliqueBold". Try to match the shortest + // attribute string -- this should be closest to pure "bold". + if (bold && (fontAttributes.length < shortestAttributeStringLength)) { + shortestAttributeStringLength = fontAttributes.length; + boldFontName = fontName; + } + } + + if ([boldFontName isEqualToString: font.fontName]) { + return font; + } + + return [UIFont fontWithName: boldFontName size: font.pointSize]; +} + +mach_timebase_info_data_t InitTimebaseInfo() +{ + mach_timebase_info_data_t timebase_info; + mach_timebase_info(&timebase_info); + absolute_to_millis = timebase_info.numer / (1000000.0 * timebase_info.denom); + millis_to_absolute = 1.0 / absolute_to_millis; + absolute_to_seconds = timebase_info.numer / (1000000000.0 * timebase_info.denom); + seconds_to_absolute = 1.0 / absolute_to_seconds; + + return timebase_info; +} diff --git a/Samples/TapRace/iphone/atlas_taprace_v2.c b/Samples/TapRace/iphone/atlas_taprace_v2.c new file mode 100644 index 00000000..dcad6de0 --- /dev/null +++ b/Samples/TapRace/iphone/atlas_taprace_v2.c @@ -0,0 +1,225 @@ +/////////////////////////////////////////////////////////////////////////////// +// GameSpy ATLAS Competition System Source File +// +// NOTE: This is an auto-generated file, do not edit this file directly. +/////////////////////////////////////////////////////////////////////////////// + +#include +#include "atlas_taprace_v2.h" + +int atlas_rule_set_version = 2; + +int ATLAS_GET_KEY(char* keyName) +{ + if(!keyName) + return 0; + + if(!strcmp("RACE_TIME", keyName)) + return RACE_TIME; + if(!strcmp("KEY_NICK", keyName)) + return KEY_NICK; + if(!strcmp("KEY_LATITUDE", keyName)) + return KEY_LATITUDE; + if(!strcmp("KEY_LONGITUDE", keyName)) + return KEY_LONGITUDE; + if(!strcmp("KEY_MULTI_PLAY", keyName)) + return KEY_MULTI_PLAY; + if(!strcmp("KEY_SINGLE_PLAY", keyName)) + return KEY_SINGLE_PLAY; + if(!strcmp("SP_RACE_TIME", keyName)) + return SP_RACE_TIME; + if(!strcmp("KEY_RACE_DATE_TIME", keyName)) + return KEY_RACE_DATE_TIME; + + return 0; +} + +char* ATLAS_GET_KEY_NAME(int keyId) +{ + if(keyId <= 0) + return ""; + + if(keyId == RACE_TIME) + return "RACE_TIME"; + if(keyId == KEY_NICK) + return "KEY_NICK"; + if(keyId == KEY_LATITUDE) + return "KEY_LATITUDE"; + if(keyId == KEY_LONGITUDE) + return "KEY_LONGITUDE"; + if(keyId == KEY_MULTI_PLAY) + return "KEY_MULTI_PLAY"; + if(keyId == KEY_SINGLE_PLAY) + return "KEY_SINGLE_PLAY"; + if(keyId == SP_RACE_TIME) + return "SP_RACE_TIME"; + if(keyId == KEY_RACE_DATE_TIME) + return "KEY_RACE_DATE_TIME"; + + return ""; +} + +int ATLAS_GET_STAT(char* statName) +{ + if(!statName) + return 0; + + if(!strcmp("AVERAGE_RACE_TIME", statName)) + return AVERAGE_RACE_TIME; + if(!strcmp("BEST_RACE_TIME", statName)) + return BEST_RACE_TIME; + if(!strcmp("CAREER_DISCONNECTS", statName)) + return CAREER_DISCONNECTS; + if(!strcmp("CAREER_DRAWS", statName)) + return CAREER_DRAWS; + if(!strcmp("CAREER_LONGEST_DRAW_STREAK", statName)) + return CAREER_LONGEST_DRAW_STREAK; + if(!strcmp("CAREER_LONGEST_LOSS_STREAK", statName)) + return CAREER_LONGEST_LOSS_STREAK; + if(!strcmp("CAREER_LONGEST_WIN_STREAK", statName)) + return CAREER_LONGEST_WIN_STREAK; + if(!strcmp("CAREER_LOSSES", statName)) + return CAREER_LOSSES; + if(!strcmp("CAREER_WINS", statName)) + return CAREER_WINS; + if(!strcmp("CURRENT_DRAW_STREAK", statName)) + return CURRENT_DRAW_STREAK; + if(!strcmp("CURRENT_LOSS_STREAK", statName)) + return CURRENT_LOSS_STREAK; + if(!strcmp("CURRENT_WIN_STREAK", statName)) + return CURRENT_WIN_STREAK; + if(!strcmp("DISCONNECT_RATE", statName)) + return DISCONNECT_RATE; + if(!strcmp("LAST_TIME_PLAYED", statName)) + return LAST_TIME_PLAYED; + if(!strcmp("LATITUDE", statName)) + return LATITUDE; + if(!strcmp("LONGITUDE", statName)) + return LONGITUDE; + if(!strcmp("NICK", statName)) + return NICK; + if(!strcmp("SP_AVERAGE_RACE_TIME", statName)) + return SP_AVERAGE_RACE_TIME; + if(!strcmp("SP_BEST_RACE_TIME", statName)) + return SP_BEST_RACE_TIME; + if(!strcmp("SP_TOTAL_PLAYS", statName)) + return SP_TOTAL_PLAYS; + if(!strcmp("SP_TOTAL_RACE_TIME", statName)) + return SP_TOTAL_RACE_TIME; + if(!strcmp("SP_WORST_RACE_TIME", statName)) + return SP_WORST_RACE_TIME; + if(!strcmp("TOTAL_COMPLETE_MATCHES", statName)) + return TOTAL_COMPLETE_MATCHES; + if(!strcmp("TOTAL_MATCHES", statName)) + return TOTAL_MATCHES; + if(!strcmp("TOTAL_RACE_TIME", statName)) + return TOTAL_RACE_TIME; + if(!strcmp("WORST_RACE_TIME", statName)) + return WORST_RACE_TIME; + + return 0; +} +char* ATLAS_GET_STAT_NAME(int statId) +{ + if(statId <= 0) + return ""; + + if(statId == AVERAGE_RACE_TIME) + return "AVERAGE_RACE_TIME"; + if(statId == BEST_RACE_TIME) + return "BEST_RACE_TIME"; + if(statId == CAREER_DISCONNECTS) + return "CAREER_DISCONNECTS"; + if(statId == CAREER_DRAWS) + return "CAREER_DRAWS"; + if(statId == CAREER_LONGEST_DRAW_STREAK) + return "CAREER_LONGEST_DRAW_STREAK"; + if(statId == CAREER_LONGEST_LOSS_STREAK) + return "CAREER_LONGEST_LOSS_STREAK"; + if(statId == CAREER_LONGEST_WIN_STREAK) + return "CAREER_LONGEST_WIN_STREAK"; + if(statId == CAREER_LOSSES) + return "CAREER_LOSSES"; + if(statId == CAREER_WINS) + return "CAREER_WINS"; + if(statId == CURRENT_DRAW_STREAK) + return "CURRENT_DRAW_STREAK"; + if(statId == CURRENT_LOSS_STREAK) + return "CURRENT_LOSS_STREAK"; + if(statId == CURRENT_WIN_STREAK) + return "CURRENT_WIN_STREAK"; + if(statId == DISCONNECT_RATE) + return "DISCONNECT_RATE"; + if(statId == LAST_TIME_PLAYED) + return "LAST_TIME_PLAYED"; + if(statId == LATITUDE) + return "LATITUDE"; + if(statId == LONGITUDE) + return "LONGITUDE"; + if(statId == NICK) + return "NICK"; + if(statId == SP_AVERAGE_RACE_TIME) + return "SP_AVERAGE_RACE_TIME"; + if(statId == SP_BEST_RACE_TIME) + return "SP_BEST_RACE_TIME"; + if(statId == SP_TOTAL_PLAYS) + return "SP_TOTAL_PLAYS"; + if(statId == SP_TOTAL_RACE_TIME) + return "SP_TOTAL_RACE_TIME"; + if(statId == SP_WORST_RACE_TIME) + return "SP_WORST_RACE_TIME"; + if(statId == TOTAL_COMPLETE_MATCHES) + return "TOTAL_COMPLETE_MATCHES"; + if(statId == TOTAL_MATCHES) + return "TOTAL_MATCHES"; + if(statId == TOTAL_RACE_TIME) + return "TOTAL_RACE_TIME"; + if(statId == WORST_RACE_TIME) + return "WORST_RACE_TIME"; + + return ""; +} + +int ATLAS_GET_STAT_PAGE_BY_ID(int statId) +{ + if(statId <= 0) + return 0; + + + //PlayerStats + if(statId == 1 || statId == 2 || statId == 3 || statId == 4 || statId == 5 || statId == 6 || statId == 7 || + statId == 8 || statId == 9 || statId == 10 || statId == 11 || statId == 12 || statId == 13 || statId == 14 || + statId == 15 || statId == 16 || statId == 17 || statId == 18 || statId == 19 || statId == 20 || statId == 21 || + statId == 22 || statId == 23 || statId == 24 || statId == 25 || statId == 26) + return 1; + + + return 0; +} + +int ATLAS_GET_STAT_PAGE_BY_NAME(char* statName) +{ + if(!statName) + return 0; + + + //PlayerStats + if(!strcmp("CAREER_WINS", statName) || !strcmp("CAREER_LOSSES", statName) || + !strcmp("BEST_RACE_TIME", statName) || !strcmp("WORST_RACE_TIME", statName) || + !strcmp("TOTAL_MATCHES", statName) || !strcmp("AVERAGE_RACE_TIME", statName) || + !strcmp("CURRENT_WIN_STREAK", statName) || !strcmp("CURRENT_LOSS_STREAK", statName) || + !strcmp("TOTAL_RACE_TIME", statName) || !strcmp("CAREER_DISCONNECTS", statName) || + !strcmp("DISCONNECT_RATE", statName) || !strcmp("CAREER_DRAWS", statName) || + !strcmp("CURRENT_DRAW_STREAK", statName) || !strcmp("CAREER_LONGEST_WIN_STREAK", statName) || + !strcmp("CAREER_LONGEST_LOSS_STREAK", statName) || !strcmp("CAREER_LONGEST_DRAW_STREAK", statName) || + !strcmp("TOTAL_COMPLETE_MATCHES", statName) || !strcmp("NICK", statName) || + !strcmp("LATITUDE", statName) || !strcmp("LONGITUDE", statName) || + !strcmp("SP_BEST_RACE_TIME", statName) || !strcmp("SP_WORST_RACE_TIME", statName) || + !strcmp("SP_TOTAL_PLAYS", statName) || !strcmp("SP_AVERAGE_RACE_TIME", statName) || + !strcmp("SP_TOTAL_RACE_TIME", statName) || !strcmp("LAST_TIME_PLAYED", statName)) + return 1; + + + return 0; +} + diff --git a/Samples/TapRace/iphone/atlas_taprace_v2.h b/Samples/TapRace/iphone/atlas_taprace_v2.h new file mode 100644 index 00000000..6857589e --- /dev/null +++ b/Samples/TapRace/iphone/atlas_taprace_v2.h @@ -0,0 +1,72 @@ +/////////////////////////////////////////////////////////////////////////////// +// GameSpy ATLAS Competition System Header File +// +// NOTE: This is an auto-generated file, do not edit this file directly. +/////////////////////////////////////////////////////////////////////////////// + +#ifndef _ATLAS_TAPRACE_V2_H_ +#define _ATLAS_TAPRACE_V2_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +extern int ATLAS_GET_KEY(char* keyName); +extern char* ATLAS_GET_KEY_NAME(int keyId); +extern int ATLAS_GET_STAT(char* statName); +extern char* ATLAS_GET_STAT_NAME(int statId); +extern int ATLAS_GET_STAT_PAGE_BY_ID(int statId); +extern int ATLAS_GET_STAT_PAGE_BY_NAME(char* statName); + +#define ATLAS_RULE_SET_VERSION 2 + +// KEYS +// Use these key ID's to report match data for your game. + +#define RACE_TIME 1 // [TYPE: int] [DESC: Player's race time for the match (milliseconds).] +#define KEY_NICK 2 // [TYPE: string] [DESC: Player's nickname to report] +#define KEY_LATITUDE 3 // [TYPE: float] +#define KEY_LONGITUDE 4 // [TYPE: float] +#define KEY_MULTI_PLAY 5 // [TYPE: int] [DESC: Key for setting multi-player match stats. Set to 1 if yes; otherwise zero] +#define KEY_SINGLE_PLAY 6 // [TYPE: int] [DESC: Key for setting single player game stats. Set to 1 if yes; otherwise zero] +#define SP_RACE_TIME 7 // [TYPE: int] [DESC: Player's race time for the match (milliseconds).] +#define KEY_RACE_DATE_TIME 8 // [TYPE: string] [DESC: Date/Time of last single play or match for a player] + +/////////////////////////////////////////////////////////////////////////////// + +// STATS +// Use these stat ID's to query aggregate statistics for your game. + +#define AVERAGE_RACE_TIME 6 // [TYPE: float] [DESC: Player's average race time per match (milliseconds/match).] +#define BEST_RACE_TIME 3 // [TYPE: int] [DESC: Player's career best match race time (milliseconds).] +#define CAREER_DISCONNECTS 10 // [TYPE: int] [DESC: Player's total number of times match disconnected.] +#define CAREER_DRAWS 12 // [TYPE: int] [DESC: Player's total number of draws (tied matches).] +#define CAREER_LONGEST_DRAW_STREAK 16 // [TYPE: int] [DESC: Player's longest streak of matches tied] +#define CAREER_LONGEST_LOSS_STREAK 15 // [TYPE: int] [DESC: Player's longest streak of matches loss] +#define CAREER_LONGEST_WIN_STREAK 14 // [TYPE: int] [DESC: Player's longest streak of matches won] +#define CAREER_LOSSES 2 // [TYPE: int] [DESC: Player's number of matches loss] +#define CAREER_WINS 1 // [TYPE: int] [DESC: Player's number of matches won] +#define CURRENT_DRAW_STREAK 13 // [TYPE: int] [DESC: Player's current streak of matches tied] +#define CURRENT_LOSS_STREAK 8 // [TYPE: int] [DESC: Player's current streak of matches loss] +#define CURRENT_WIN_STREAK 7 // [TYPE: int] [DESC: Player's current streak of matches won] +#define DISCONNECT_RATE 11 // [TYPE: float] [DESC: Player's disconnect rate (disconnects/matches).] +#define LAST_TIME_PLAYED 26 // [TYPE: string] [DESC: Date/Time of last single play or match for a player] +#define LATITUDE 19 // [TYPE: float] [DESC: Latitude for Player's location after a single or multiplayer game] +#define LONGITUDE 20 // [TYPE: float] [DESC: Longitude for a Player's location after a single or multiplayer game] +#define NICK 18 // [TYPE: string] [DESC: Player's nickname for leaderboard] +#define SP_AVERAGE_RACE_TIME 24 // [TYPE: float] [DESC: Player's average race time per single plays (milliseconds/play).] +#define SP_BEST_RACE_TIME 21 // [TYPE: int] [DESC: Player's career best match race time (milliseconds).] +#define SP_TOTAL_PLAYS 23 // [TYPE: int] [DESC: Total number of single plays that were completed] +#define SP_TOTAL_RACE_TIME 25 // [TYPE: int] [DESC: Player's total race time for all simgle plays (milliseconds).] +#define SP_WORST_RACE_TIME 22 // [TYPE: int] [DESC: Player's career worst single play time (milliseconds).] +#define TOTAL_COMPLETE_MATCHES 17 // [TYPE: int] [DESC: Total number of matches where the game went to completion (all win/loss/draw results).] +#define TOTAL_MATCHES 5 // [TYPE: int] [DESC: Total number of matches that were completed] +#define TOTAL_RACE_TIME 9 // [TYPE: int] [DESC: Player's total race time for all matches (milliseconds).] +#define WORST_RACE_TIME 4 // [TYPE: int] [DESC: Player's career worst match time (milliseconds).] + + +#ifdef __cplusplus +} +#endif + +#endif // _ATLAS_TAPRACE_V2_H_ diff --git a/Samples/TapRace/iphone/main.mm b/Samples/TapRace/iphone/main.mm new file mode 100644 index 00000000..39fd26f8 --- /dev/null +++ b/Samples/TapRace/iphone/main.mm @@ -0,0 +1,19 @@ +// Copyright Notice: This file is part of the GameSpy SDK designed and +// developed by GameSpy Industries. Copyright (c) 2009 GameSpy Industries, Inc. + +#import +#import "sake/sakeRequest.h" + +int main(int argc, char* argv[]) +{ + NSAutoreleasePool* pool = [[NSAutoreleasePool alloc] init]; + + // code to wake up 3G + NSURL * url = [[NSURL alloc] initWithString:[NSString stringWithCString:"http://www.g1a2m3e4s5p6y.com"]]; + [NSData dataWithContentsOfURL:url]; + [url release]; + + int ret = UIApplicationMain(argc, argv, nil, nil); + [pool release]; + return ret; +} \ No newline at end of file