Version 0.3 (Experimental)
Current as of 01/04/2012. If you see or have any problems let us know!
Setting up the C++ API
Download precompiled version: playtomic-cpp-0.3-precompiled.zip, or get it from GitHub (with examples)
You will also need to download boost and curl lib to compile the API. You can download the dependencies package from here.
Visual Studio
Including prebuilt DLL
Configuring project properties
In C/C++ tab
- Go to General tab
- Add "(PlaytomicPath)/Include; (DependenciesPath)/Boost;(DependenciesPath)/currlib/include" to Additional include directories
Go to Preprocessor tab
- Add "LINK_DLL; BOOST_ALL_NO_LIB; JSON_DLL" to Preprocessor Definitions
In Linker tab
- Go to General tab
- Add (PlaytomicPath)/Lib/$(Configuration)Dll to "Additional Library Directories"
In Input tab
- Add "Playtomic.lib" To Additional Dependencies
Using the prebuilt library
Configuring project properties
In C/C++ tab
- Go to General tab
- Add "(PlaytomicPath)/Include; (PlaytomicPath)/Include/json/include; (PlaytomicPath)/Include/CurlWrap; (PlaytomicPath)/Include/Tools; (DependenciesPath)/Boost; (DependenciesPath)/currlib/include" to your include directories
Go to Preprocessor tab
- Add "BOOST_ALL_NO_LIB" to Preprocessor Definitions
In Linker tab
- Go to General tab
- Add (PlaytomicPath)/Lib/$(Configuration) to "Additional Library Directories"
- Go to Input tab
- Add "libcurld.lib;Playtomic.lib;winmm.lib;ws2_32.lib;wldap32.lib" To Additional Dependencies
Initialize Playtomic API in your game with your game credentials
the autoupdate flag defines if the api create a thread to update the timers if you leave this flag as false you should remeber to update playtomic in your main loop
Playtomic::CPlaytomic *mInstance = new Playtomic::CPlaytomic( SWFID, GUID, true);
mInstance->Init();
Xcode MacOs
Using the prebuilt library
Configuring project properties
In your project target
- Go to Build Settings
- Go to Search Paths
- Add "$(PLAYTOMIC_PATH)/Tools $(PLAYTOMIC_DEPENDENCIES_PATH)/macos/Boost/** $(PLAYTOMIC_PATH)/ $(PLAYTOMIC_PATH)/json/** $(PLAYTOMIC_PATH)/CurlWrap/**" to Header Search Paths
- Add ""$(PLAYTOMIC_DEPENDENCIES_PATH)/macos/Boost/lib/debug" "(PLAYTOMIC_PATH)/Playtomic/Debug" $(PLAYTOMIC_DEPENDENCIES_PATH)/macos/Boost/lib/release"" to library Search Paths
Go to Build Phases tab
In Link Binary With Libraries
- Add "libPlaytomic.a"
- Add "libcurl.dylib"
- Add "libboost_thread.a"
Initialize Playtomic API in your game with your game credentials
the autoupdate flag defines if the api create a thread to update the timers if you leave this flag as false you should remeber to update playtomic in your main loop
Playtomic::CPlaytomic *mInstance = new Playtomic::CPlaytomic( SWFID, GUID, true);
mInstance->Init();
Xcode iOS
Using the prebuilt framework
Add these frameworks to your game by clicking the project, then selecting the target -> build phases -> expanding Link Binary with Libraries.
- Curl.framework
- boost.framework
- Playtomic.framework
Building from source
- The playtomic xcode project will call a script to rebuild the framework you can find it at $(project_dir)/bild/Framework/
- You can find the Curl.framework and boost.framework in the precompiled dependencies $(dependencies_dir)/macos/
Initialize Playtomic API in your game with your game credentials
the autoupdate flag defines if the api create a thread to update the timers if you leave this flag as false you should remeber to update playtomic in your main loop
Playtomic::CPlaytomic *mInstance = new Playtomic::CPlaytomic( SWFID, GUID, true);
mInstance->Init();
Android native
IMPORTANT
To use playtomic in your project you need to compile the project using crystax-ndk since the official ndk don't support all the c++ features we use
Using the prebuilt framework
in your android.mk add:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
PLAYTOMIC_PATH := #your path to playtomic should go here
LOCAL_MODULE := Playtomic-prebuilt
LOCAL_SRC_FILES := $(PLAYTOMIC_PATH)/libs/$(TARGET_ARCH_ABI)/libPlaytomic.so
include $(PREBUILT_SHARED_LIBRARY)
include $(CLEAR_VARS)
#...
#...
#in your project add this
LOCAL_CFLAGS += -fexceptions
LOCAL_CFLAGS += -D__ARM_ARCH_5__
LOCAL_CFLAGS += -D__ANDROID__
PLAYTOMIC_PATH := #your path to playtomic should go here
LOCAL_C_INCLUDES += $(PLAYTOMIC_PATH)/Dependencies/Android/Boost/ \
$(PLAYTOMIC_PATH)/CurlWrap/ \
$(PLAYTOMIC_PATH)/Tools/ \
$(PLAYTOMIC_PATH)/json/include \
$(PLAYTOMIC_PATH)/Dependencies/Android/curllib/include/ \
$(PLAYTOMIC_PATH)/Playtomic/ \
$(PLAYTOMIC_PATH)/
Permision
In your application AndroidManifest.xml you need to add this
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
Initialize Playtomic API in your game with your game credentials
the autoupdate flag defines if the api create a thread to update the timers if you leave this flag as false you should remeber to update playtomic in your main loop
| Param |
Type |
Description |
| gameId |
int |
your game id number SWFID you can get it from your dashboard |
| gameguid |
std::string& |
your game GUID number SWFID you can get it from your dashboard |
| javaVMPtr |
JavaVM* |
a pointer to the java virtual machine |
| activity |
jobject |
your aplication object, required to access some android sdk features you should make it global calling NewGlobalRef() you can find more information about this in any JNI reference |
Playtomic::CPlaytomic *mInstance = new Playtomic::CPlaytomicAndroid(SWFID, GUID, vmPtr, activity, autoUpdate);
mInstance->Init();
Analytics
Views
A view occurs whenever somebody views your game. This should go somewhere very early in your code like before the preloader.
Log()->View();
Plays
A play occurs when someone begins a game by clicking your play button or whatever action is relevant to your game. A person can get between 0 and many plays during a single view.
They're tracked seperately because the main menu is the first thing the player sees after loading and splash animations, and if they don't like it they'll leave.
Play time, countries and sources
This data is logged automatically when you send other events.
void StartedGame
{
gPlaytomic->Log()->Play();
// continue starting game
...
Freezing, unfreezing and force-sending events
Note: This is inherited from the Flash version which runs on a single thread, and is unlikely to be necessary in C++ games.
Events are sent in batches each time the play timer updates (every 30 seconds after the first minute). If you want to ensure an event gets sent:
Log()->ForceSend();
Some games are very resource-intensive and the API might send off a batch of events at exactly the wrong time. You can freeze and unfreeze the logging at any time by:
Log()->Freeze();
Log()->UnFreeze();
When logging is frozen all events are queued but not sent until you unfreeze the API.
Custom metrics
Custom metrics allow you to track how many people do something in your game, for instance how many play on easy, medium or hard, or how many play in English vs. Spanish, or how many view the tutorial or skip it. Anything you think can help you improve your game.
You can define custom metrics directly in your game and they will be added to Playtomic automatically. Groups can be automatically assigned via an optional second parameter or set up later in the dashboard.
You can also limit custom metrics to unique-per-view occurances with a third parameter.
Log()->CustomMetric("ViewedCredits",""); // metric, names must be alphanumeric
Log()->CustomMetric("Credits", "Screens"); // metric with group, groups must be alphanumeric
Log()->CustomMetric("ClickedSponsorsLinks", "Links", true); // unique metric with group
Level metrics
Level metrics track events on a per-level basis so you can drill down into your difficulty and retention by identifying which levels have problems and what those problems are.
There are three types of level metrics - counters (like custom metrics), ranged-value and average-value.
Note that you can pass either an integer level number or a string name of the level. If your game is not using numeric levels (eg an escape game) then you would pass the name of each screen / area as a level.
You can define level metrics directly in your game and they will be added to Playtomic automatically.
Level metrics support unique-per-play occurrances via an optional second parameter. If the player starts a new game they will be tracked again.
Counter metrics
These metrics track how many times something occurs in your levels, for instance deaths and restarts.
One of the most valuable pieces of data you can track is how many people begin each level, this allows you to see where you lose players.
Log()->LevelCounterMetric("Deaths", level_number); // names must be alphanumeric
Log()->LevelCounterMetric("Restarts", "LevelName"); // level names must be alphanumeric
Log()->LevelCounterMetric("Restarts", "LevelName", true); // unique only
Average metrics
These metrics track the average of something in your levels, for instance the average time to finish a level or the average number of retries. It also tracks the minimum and maximums.
Log()->LevelAverageMetric("Time", level_number, seconds);
Log()->LevelAverageMetric("Retries", level_number, retries);
Log()->LevelAverageMetric("Retries", level_number, retries, true); // unique only
Ranged-value metrics
These metrics track metrics with values, for instance in a golf game you might track how many shots it takes to complete each level, or you might track the % of coins collected on each level.
Log()->LevelRangedMetric("Shots", level_number, shots);
Log()->LevelRangedMetric("PercentCoinsCollected", level_number, int(coins / coinstotal * 100));
Log()->LevelRangedMetric("Shots", level_number, shots, true); // unique only
Heatmaps
Heatmaps allow you to map activity (clicks, deaths, first deaths, or anything else you want) against an image you upload in the dashboard.
Log()->Heatmap("Metric", "Heatmap", x, y);
In the dashboard you upload a background image for the heatmap, and then it is shared by any metrics using it.
Link tracking
Link tracking allows you to keep track of how many people open URLs in your game, providing you information on unique, total and failed clicks that can fully audited to allow you to identify good sources of traffic and sites that block links.
Link tracking does not change your URL or redirect traffic through a different url!
How to track a link
You track a link by passing a URL and some other information to the API. The API will return true or false if the link opens, everything else is automatic.
Link()->Open(const std::string& url, const std::string& name,const std::string& group);
Link()->Open("http://website.com/?gameref=my_game", "PlayMoreGames", "Sponsor");
Domain Totals
When you track a link it automatically also tracks the totals for the domain in a group it creates called DomainTotals. The DomainTotals allows you to see how many unique, total and failed clicks occurred for a single domain even if you have multiple, different links to it (eg walkthrough or differently-structured sponsor links).
Parse custom databases
This feature is provided in conjunction with Parse so you will need to create your account with them before you can use it.
Custom databases allow you to store and manipulate any data you want in any way you want, like:
- User registration
- Mailing list (and send through MailChimp)
- Saved games
- Surveys & feedback
- Messaging
- etc
The PFObject
This object represents a piece of data in your database. It has the following properties:
| Property |
Type |
Description |
| ObjectId |
String |
If your object already exists in your database it will have this unique identifier. |
| ClassName |
String |
The 'type' of data this is - user, score, etc |
| Data |
Object |
Your actual data to save, eg po.Data.FirstName |
| Pointers |
Vector.<PFObject> |
Any objects related to this (see below) |
| UpdatedAt |
Date |
When the object was last updated in the database |
| CreatedAt |
Date |
When the object was created in the database |
| Password |
String |
If an object has a password when it is saved the password must be included to update it. |
The PFQuery
This object allows you to construct a query to run against your database to find objects you've saved.
| Property |
Type |
Description |
| ClassName |
String |
The 'type' of data to return - user, score, etc |
| WhereData |
Object |
Data to filter by, eg pq.WhereData.FirstName = 'ben' |
| WherePointers |
Vector.<PFObject> |
Any objects related to this (see below) |
| Order |
String |
What field (from Data) to sort by |
| Limit |
int |
Numner of results to return |
The PFPointer
This object allows you to create a relationship between two objects - for instance my saved game might point to the inventory items I have.
| Property |
Type |
Description |
| FieldName |
String |
The field this refers to (eg 'FirstName') |
| PObject |
PFObject |
The object to point to |
Saving / updating an object
You can save any object you want by creating a PFObject and sending it to Parse.Save. An object will save if it does not have an ObjectId, if it does then it will update.
var po:PFObject = new PFObject();
po.ClassName = "savedgames";
po.Data.Name = "Ben";
po.Data.Level = 1;
po.Data.Stage = 7;
Parse.Save(po, SaveComplete);
function SaveComplete(po:PFObject, response:Response):void
{
if(response.Success)
{
trace("Saved object: " + po.ObjectId);
}
else
{
trace("Failed to save: " + response.ErrorMessage);
}
}
Loading an object
You can load any object as long as you know it's ClassName and ObjectId (which you might obtain from a PFPointer).
var po:PFObject = new PFObject();
po.ClassName = "savedgames";
po.ObjectID = "asdfasdf";
Parse.Load(po, LoadComplete);
function LoadComplete(po:PFObject, response:Response):void
{
if(response.Success)
{
trace("Loaded object: " + po.ObjectId);
}
else
{
trace("Failed to load: " + response.ErrorMessage);
}
}
Finding objects / querying your database
Using the PFQuery object you can locate data in your database:
var pq:PFQuery = new PFQuery();
pq.ClassName = "savedgames";
pq.WhereDate.Name = "Ben";
pq.Limit = 5;
Parse.Find(pq, FindComplete);
function FindComplete(results:Array, response:Response):void
{
if(response.Success)
{
trace("Loaded objects");
for(var i=0; i<results.length; i++)
trace(results[i].ObjectId);
}
else
{
trace("Failed to find: " + response.ErrorMessage);
}
}
Deleting objects
You can delete an object easily:
// po is a PFObject you've previously loaded from somewhere
Parse.Delete(po, DeleteComplete);
function DeleteComplete(response:Response):void
{
if(response.Success)
{
trace("Deleted object");
}
else
{
trace("Failed to delete: " + response.ErrorMessage);
}
}
Level sharing
The level sharing API provides a way to store and retrieve user-generated content for your game. It can operate anonymously or authenticated via any 3rd party service you're already using.
The PlayerLevel class
Saving and listing levels uses this class to represent a level.
| Property |
Type |
Description |
| PlayerName |
String |
The name of the player (or "anonymous", "guest", etc), or the name provided by any 3rd party API. |
| PlayerId |
String |
If you're working under a 3rd party API you can include the player's user id |
| PlayerSource |
String |
If you're working under a 3rd party API you can specify which, eg "gamersafe" or "mochicoins" |
| Name |
String |
The name of the level |
| Data |
String |
The data for the level. You can Base 64 encode a ByteArray to a string if necessary. |
| Votes |
int |
The number of votes the level has |
| Score |
int |
The sum of all votes the level has |
| Plays |
int |
The number of plays the level has |
| Rating |
int |
The rating the level has (score / votes) |
| SDate |
Date |
The date of the level, determined automatically by Playtomic |
| RDate |
String |
The relative date of the level eg "7 minutes ago", determined automatically by Playtomic |
| CustomData |
Object |
Any additional data you want to (or have) attached to a score, like the level the player reached or what character they used |
| Thumbnail |
String |
The URL of the thumbnail, unless you generate them from data (recommended) |
Saving levels
PlayerLevels()->SaveLevel(CLevel& level)
| Parameter |
Type |
Description |
| level |
PlayerLevel |
An instance of PlayerLevel holding the level data |
Example saving level:
void SaveLevel()
{
CLevel level("My level","PlayerName", "Playerid","Any data");
SLevelListPtr list = gPlaytomic->PlayerLevels()->SaveLevel(level);
if(list->sSucceded)
{
std::cout << "Level saved successfully, the level parameter is ready for use!" ;
}
else
{
// failed because of list->sErrorCode
}
}
Rating levels
Levels can be rated 1 - 10 by players. Rating can be done anonymously with some protection against repeat voting, or bound to PlayerIds if you specify them.
PlayerLevels()->RateLevelId(const std::string& levelId, int rating);
| Parameter |
Type |
Description |
| levelid |
String |
a_player_level.LevelId |
| rating |
int |
1 - 10 |
Example rating level:
void Rate()
{
CPlaytomicResponsePtr response = Playtomic::gPlaytomic->PlayerLevels()->RateLevelId(name,rating);
if(response->ResponseSucceded())
{
std::cout << "Rating complete" << std::endl;
}
else
{
// Rating failed because of response->ResponseError()
}
}
Listing levels
Listing levels can be done by popular or newest, with optional filtering by date ranges and/or custom data.
PlayerLevels()->List(const std::string& mode, int page, int perPage,
bool includeData, bool includeThumbs,
const CustomData& customFilter)
| Parameter |
Type |
Description |
| mode |
string |
'popular' or 'newest' |
| page |
int |
The page you want |
| perPage |
int |
The number of levels to return |
| includeData |
bool |
specify true if you want level data returned with the list |
| includeThumb |
bool |
specify true if you want level data thumbnail url included |
| customFilter |
CustomData |
filter by custom data you've saved with levels |
An example listing levels:
void ListLevels()
{
SLevelListPtr list = gPlaytomic->PlayerLevels()->List("",1,10,false,false,customData);
if(list->sSucceded)
{
std::list<Playtomic::CLevel>::iterator it = list->sLevelList.begin();
for (; it != list->sLevelList.end(); it++)
{
std::cout << "name: " << it->GetName() << " LevelId: "
<< (it->GetLevelId()) << "PlayerName: " << it->GetPlayerName() <<
"date: "<< it->GetRelativeDate() <<std::endl;
}
}
else
{
// Level list failed to load because of list->sErrorCode
}
}
Loading levels
If you do not include the data when you load lists of levels then you can request it seperately:
PlayerLevels()->LoadLevel(const std::string& levelId);
| Parameter |
Type |
Description |
| levelid |
String |
a_player_level.LevelId |
An example loading a single level
void Load()
{
SLevelListPtr list = gPlaytomic->PlayerLevels()->LoadLevel( level.GetLevelId());
if(list->sSucceded)
{
//"Level has been loaded, now you can begin playing it"
}
else
{
// level failed to load because of list->sErrorCode
}
}
Asynchronous Version
Asynchronous request are similar to the synchronous request but you need to set a delegate that implement IPlayerLevelDelegate to preocess the request.
To set the delegate you need to call:
CPlayerLevels::SetDelegate(IPlayerLevelDelegate* targetDelegate)
When you use the asynchronous calls you need to inherit from IPlayerLevelDelegate and implement its four pure virtual methods:
class IPlayerLevelDelegate
{
public:
virtual void RateLevelComplete(CPlaytomicResponsePtr& result)=0;
virtual void LevelListComple(SLevelList& result)=0;
virtual void SaveLevelComple(SLevelList& result)=0;
virtual void LoadLevelComplete(SLevelList& result)=0;
};
An example
class CMyClass : public IPlayerLevelDelegate
{
public:
virtual void RateLevelComplete(CPlaytomicResponsePtr& result);
virtual void LevelListComple(SLevelList& result);
virtual void SaveLevelComple(SLevelList& result);
virtual void LoadLevelComplete(SLevelList& result);
void CallAsyncRequest();
};
void MyClass::CallAsyncRequest()
{
CustomData customData;
gPlaytomic->PlayerLevels()->SetDelegate(this);
Playtomic::CLevel level( "Test Level", "My Name", "234561", "there should be data here");
gPlaytomic->PlayerLevels()->SaveLevelAsync(level);
gPlaytomic->PlayerLevels()->ListAsync("",1,10,false,false,customData);
gPlaytomic->PlayerLevels()->LoadLevelAsync("4e9ef2b94d81233f30921596"); //hardcoded id from the dashboard
gPlaytomic->PlayerLevels()->RateLevelIdAsync("4e9ef2b94d81233f30921596",10.0);
}
void MyClass::RateLevelComplete( CPlaytomicResponsePtr& result )
{
if(result->ResponseSucceded())
{
std::cout << "rating succeded!" << std::endl;
}
else
{
std::cout << "rating failed! Err Code:"
<< result->ResponseError() << std::endl;
}
}
void MyClass::LevelListComple( SLevelList& result )
{
std::cout << std::endl << std::endl << "levels"
<< " page: 0" << std::endl << std::endl;
std::list<CLevel>::iterator it = result.sLevelList.begin();
for (; it != result.sLevelList.end(); it++)
{
std::cout << "name: " << it->GetName() << " LevelId: "
<< (it->GetLevelId()) << "PlayerName: " << it->GetPlayerName()
<< "date: " << it->GetRelativeDate() << std::endl;
}
}
void MyClass::SaveLevelComple(SLevelList& result )
{
if(!result.sSucceded)
{
std::cout << "failed to save level, error code :" << result.sErrorCode;
}
}
void MyClass::LoadLevelComplete( SLevelList& result )
{
if(result.sSucceded)
{
if(result.sLevelList.size() == 0)
{
std::cout << "could not find the entry:" << std::endl;
}
else
{
std::list<CLevel>::iterator it = result.sLevelList.begin();
for (; it != result.sLevelList.end(); it++)
{
std::cout << "name: " << it->GetName() << " LevelId: "
<< (it->GetLevelId()) << "PlayerName: " << it->GetPlayerName()
<< "date: " << it->GetRelativeDate() << std::endl;
}
}
}
else
{
std::cout << "failed to get the level!" << std::endl;
std::cout << "error number: " << result.sErrorCode << std::endl;
}
}
Leaderboards
The Leaderboards API gives you very flexible high and low score leaderboards. They can be created in your game dynamically or set up in the edit leaderboards page.
The PlayerScore class
The leaderboards use the CScore class for representing the players' scores.
| Property |
Type |
Description |
| Name |
String |
The player name. This can be by the player or provided by any 3rd party |
| Points |
int |
The player's score |
| Rank |
int |
The rank based on your listing parameters. |
| RDate |
String |
The relative date of the score eg "7 minutes ago", determined automatically by Playtomic |
| CustomData |
CustomData |
Any additional data you want to (or have) attached to a level. |
Submitting scores
Score submission is handled by:
Leaderboards()->SaveTable(const std::string& tableName, const CScore& score, bool highest, bool allowDuplicates);
| Parameter |
Type |
Description |
| table |
String |
The score table to submit to, alphanumeric |
| score |
CScore |
An instance of CScore which contains score information |
| highest |
bool |
Leaderboard tables can be created dynamically from within your game so you specify the mode. |
| allowDuplicate |
bool |
If you don't allow duplicates new, worse scores will not be saved by a player. |
void SubmitScore()
{
CScore simpleScore("Player Name", 1000);
// submit to the highest-is-best table "highscores"
response = gPlaytomic->Leaderboards()->SaveTable("highscores", simpleScore,true,true);
if(response->ResponseSucceded())
{
std::cout << "new score entry success!" <<std::endl;
}
else
{
std::cout << "failed to save the score!" << std::endl;
std::cout << "error number: " << response->ResponseError() << std::endl;
}
// submit an advanced score with custom data
CustomData customData;
customData.insert(std::make_pair("Character", playerCharacter));
customData.insert(std::make_pair("Level", currentLevel));
CScore advancedScore("Player Name", 1000, customData);
response = gPlaytomic->Leaderboards()->SaveTable("highscores", advancedScore,true,true);
if(response->ResponseSucceded())
{
std::cout << "new score entry success!" << std::endl;
}
else
{
std::cout << "failed to save the score!" << std::endl;
std::cout << "error number: " << response->ResponseError() << std::endl;
}
}
Showing scores
Scores are loaded via a simple method that returns an array of CScore objects so you can display the data in your leaderboard.
Leaderboards()->ListTable(const std::string& tableName, bool highest, const std::string& mode, int page, int perPage, const CustomData& customFilter);
| Parameter |
Type |
Description |
| table |
String |
Your leaderboard table name, must be alphanumeric |
| highest |
bool |
if the table does not exist it will be created with this mode. |
| mode |
String |
The list mode can return scores from: "last7days" or "last30days" or "alltime" or "today" or "newest" which gives you a stream of unranked scores |
| page |
int |
The page you want |
| perPage |
int |
The number of scores to return |
| customFilter |
CustomData |
Filter by scores with specific CustomData |
void ShowScores()
{
CustomData filter;
SSCoreTablePtr table = gPlaytomic->Leaderboards()->ListTable("Demo Table",true,"last7days", 1,10 , filter);
if (table->sSucceded)
{
std::cout << std::endl << std::endl << "scoretable"
<< " page: 1" << std::endl << std::endl;
std::list<CScore>::iterator it = table->sScoreList.begin();
for (; it != table->sScoreList.end(); it++)
{
std::cout << "name: " << it->GetName() << " Score: "
<< (int)(it->GetPoints()) << "Rank: " << it->GetRank() <<
"date: "<< it->GetRelativeDate() << std::endl;
}
}
}
Save and list
You can now submit scores and at the same time return the leaderboard page that that score is on.
This combines the Save and List approaches from above:
gPlaytomic->Leaderboards()->SaveAndListTable(const std::string& tableName, const CScore& score, bool highest, bool allowDuplicates, const std::string& mode, int perPage, const CustomData& customFilter)
Some important notes:
- You don't specify a page, you specify the PerPage (default 20) and it will automatically decide which page to return based on the submitted score's rank. If you submit the 1187th best score at 30 per page then it will show you page 39, scores #1170 - #1200.
- Ranks are returned, so you are not viewing the top N scores you are viewing the top N scores from rank X onwards.
Asynchronous Version
Asynchronous request are similar to the synchronous request but you need to set a delegate that implement ILeaderboardDelegate to preocess the request.
To set the delegate you need to call
CLeaderboard::SetDelegate(ILeaderboardDelegate* targetDelegate)
when you use the asynchronous calls you need to inherit from ILeaderboardDelegate and implement its three pure virtual methods:
class ILeaderboardDelegate
{
public:
virtual void SaveComplete(CPlaytomicResponsePtr& result)=0;
virtual void ListTableComple(SSCoreTable& result)=0;
virtual void SaveAndListComple(SSCoreTable& result)=0;
};
An example
class CMyClass : public ILeaderboardDelegate
{
public:
virtual void SaveComplete(CPlaytomicResponsePtr& result);
virtual void ListTableComple(SSCoreTable& result);
virtual void SaveAndListComple(SSCoreTable& result);
void CallAsyncRequest();
};
void MyClass::CallAsyncRequest()
{
CustomData filter;
CScore scoreData;
gPlaytomic->Leaderboards()->SetDelegate(this);
scoreData.SetDefaultValues("Player name", 100);
gPlaytomic->Leaderboards()->SaveTableAsync("Demo Table", scoreData,true,true);
gPlaytomic->Leaderboards()->SaveAndListTableAsync("Demo Table", scoreData,true,true,"last7days",10 , filter);
gPlaytomic->Leaderboards()->ListTableAsync("Demo Table",true,"last7days", 1,10 , filter);
}
void MyClass::SaveComplete( CPlaytomicResponsePtr& result )
{
if(result->ResponseSucceded())
{
std::cout << "new score entry success!" << std::endl;
}
else
{
std::cout << "failed to save the score!" << std::endl;
std::cout << "error number: " << result->ResponseError() << std::endl;
}
}
void MyClass::ListTableComple( SSCoreTable& result )
{
if (result.sSucceded)
{
std::cout << std::endl << std::endl << "scoretable"
<< " page: 0" << std::endl << std::endl;
std::list<CScore>::iterator it = result.sScoreList.begin();
for (; it != result.sScoreList.end(); it++)
{
std::cout << "name: " << it->GetName() << " Score: "
<< (int)(it->GetPoints()) << "Rank: " << it->GetRank()
<< "date: "<< it->GetRelativeDate() << std::endl;
}
}
}
void MyClass::SaveAndListComple( SSCoreTable& result )
{
if (result.sSucceded)
{
std::cout << std::endl << std::endl << "scoretable"
<< " page: 0" << std::endl << std::endl;
std::list<CScore>::iterator it = result.sScoreList.begin();
for (; it != result.sScoreList.end(); it++)
{
std::cout << "name: " << it->GetName() << " Score: "
<< (int)(it->GetPoints()) << "Rank: " << it->GetRank()
<< "date: " << it->GetRelativeDate() << std::endl;
}
}
else
{
std::cout << "failed to save the score!" << std::endl;
std::cout << "error number: " << result.sErrorCode << std::endl;
}
}
GameVars
GameVars let you change the value of key variables in your game any time you want. They must be configured in the edit GameVars page in advance.
It is called via:
GameVars()->Load();
GameVars()->LoadAsync(RequestDelegate targetDelegate);
The targetDelegate parameter is your function that receives a CPlaytomicResponsePtr reference
// our variables with default, original values
int gBaseHitPoints = 100;
int gBaseGold = 50;
void InitialiseGame()
{
// load GameVars
CPlaytomicResponsePtr response = gPlaytomic->GameVars()->Load();
if (response->ResponseSucceded())
{
FData array;
array = response->ResponseData();
if(array.size() == 0)
{
std::cout << " empty game vars" << std::endl;
break;
}
else
{
FData value;
for (size_t i = 0; i < array.size(); i++)
{
value = array[i];
FData name;
name = value.get("Name", name);
FData varValue;
varValue = value.get("Value",varValue);
std::cout << name.asString() << "=" << varValue.asString() << std::endl;
}
}
}
// carry on initializing your game
}
Asynchronous Version
// our variables with default, original values
int gBaseHitPoints = 100;
int gBaseGold = 50;
void InitialiseGame()
{
// load GameVars
gPlaytomic->GameVars()->LoadAsync(fastdelegate::MakeDelegate(this,&MyClass::GameVarsDelegate));
// carry on initializing your game
}
void MyClass::GameVarsDelegate(CPlaytomicResponsePtr& response)
{
if (response->ResponseSucceded())
{
FData array;
array = response->ResponseData();
if(array.size() == 0)
{
std::cout << " empty game vars" << std::endl;
break;
}
else
{
FData value;
for (size_t i = 0; i < array.size(); i++)
{
value = array[i];
FData name;
name = value.get("Name", name);
FData varValue;
varValue = value.get("Value",varValue);
std::cout << name.asString() << "=" << varValue.asString() << std::endl;
}
}
}
}
Geolocation
The GeoIP service identifies which country the player is from, returning their country code and name.
It is called via:
GeoIP()->Load();
GeoIP()->LoadAsync(RequestDelegate targetDelegate)
The delegate method receives a CPlaytomicResponsePtr reference with an object that has Code and Name properties:
Example
void MyClass::GetLocation()
{
response = Playtomic::gPlaytomic->GeoIP()->Load();
if (response->ResponseSucceded())
{
FData geoIpInfo;
geoIpInfo = response->ResponseData();
FData code,name;
code = geoIpInfo.get("Code",code);
name = geoIpInfo.get("Name", name);
std::cout << "Code: " << code.asString() << std::endl;
std::cout << "Name: " << name.asString() << std::endl;
}
}
Asynchronous Version
void MyClass::GetLocation()
{
Playtomic::gPlaytomic->GeoIP()->LoadAsync(fastdelegate::MakeDelegate(this,&MyClass::LocationDelegate));
}
void MyClass:::LocationDelegate(CPlaytomicResponsePtr& response)
{
if (response->ResponseSucceded())
{
FData geoIpInfo;
geoIpInfo = response->ResponseData();
FData code,name;
code = geoIpInfo.get("Code",code);
name = geoIpInfo.get("Name", name);
std::cout << "Code: " << code.asString() << std::endl;
std::cout << "Name: " << name.asString() << std::endl;
}
}
Data lookup
Note: You must enable this functionality in each game's settings. By default it is disabled becaue it can expose your game data.
The Data class in the API allows you to retrieve any of your game data to display in your game.
Each function for retrieving data takes an optional parameters object for day, month and year, with default values of 0.
- When day, month and year are specified you will receive data for that specific day.
- When month and year are specified you will receive data for that specific month.
- When day, month and year are unspecified or 0 then you will receive data for all time.
Views, Plays and Play Time
The Views, Plays and PlayTime functions returns this data to you for processing:
| Parameter |
Type |
Description |
| day |
int |
if 0 you will receive data for that specific month. |
| month |
int |
if 0 you will receive data for that specific year |
| year |
int |
if 0 you will receive data for all time. |
Synchronous Version
Data()->Views(int month = 0, int year= 0);
Data()->Views(int day, int month, int year);
Data()->Plays(int month = 0, int year= 0);
Data()->Plays(int day, int month, int year);
Data()->Playtime(int month = 0, int year= 0);
Data()->Playtime(int day, int month, int year);
Asynchronous Version
Data()->ViewsAsync(RequestDelegate targetDelegate);
Data()->ViewsAsync(int day, int month, int year, RequestDelegate targetDelegate);
Data()->ViewsAsync(int month, int year, RequestDelegate targetDelegate);
Data()->PlaysAsync(RequestDelegate targetDelegate);
Data()->PlaysAsync(int day, int month, int year, RequestDelegate targetDelegate);
Data()->PlaysAsync(int month, int year, RequestDelegate targetDelegate);
Data()->PlaytimeAsync(RequestDelegate targetDelegate);
Data()->PlaytimeAsync(int day, int month, int year, RequestDelegate targetDelegate);
Data()->PlaytimeAsync(int month, int year, RequestDelegate targetDelegate);
| Parameter |
Type |
Description |
| day |
int |
if 0 you will receive data for that specific month. |
| month |
int |
if 0 you will receive data for that specific year |
| year |
int |
if 0 you will receive data for all time. |
| targetDelegate |
RequestDelegate |
delegate method to call, you can create the delegate using fastdelegate::MakeDelegate(instance, method pointer) e.x fastdelegate::MakeDelegate(this,&MyClass::MethodName) |
gPlaytomic->Data()->ViewsAsync(fastdelegate::MakeDelegate(this,&MyClass::ViewFinish));
gPlaytomic->Data()->Plays(22,3, 2010,fastdelegate::MakeDelegate(this,&MyClass::PlaysFinish)); // plays on March 22, 2010
gPlaytomic->Data()->PlaytimeAsync(3, 2010,fastdelegate::MakeDelegate(this,&MyClass::PlayTimeFinish)); // play time on March, 2010
void MyClass::ViewFinish(CPlaytomicResponsePtr& response)
{
if (response->ResponseSucceded())
{
FData value;
value = response->ResponseData().get("Value",value);
std::cout << "views = " << value.asString() << std::endl;
}
else
{
std::cout << "response failed error code = " << response->ResponseError() << std::endl;
}
}
void MyClass::PlaysFinish(CPlaytomicResponsePtr& response)
{
if (response->ResponseSucceded())
{
FData value;
value = response->ResponseData().get("Value",value);
std::cout << "Plays = " << value.asString() << std::endl;
}
else
{
std::cout << "response failed error code = " << response->ResponseError() << std::endl;
}
}
void MyClass::PlaytimeFinish(CPlaytomicResponsePtr& response)
{
if (response->ResponseSucceded())
{
FData value;
value = response->ResponseData().get("Value",value);
std::cout << "play time = " << value.asString() << std::endl;
}
else
{
std::cout << "response failed error code = " << response->ResponseError() << std::endl;
}
}
Custom metrics
The CustomMetric function returns data about a custom metric to your function, which receives the same parameters as views/plays/play time above.
Synchronous Version
Data()->CustomMetric(const std::string& name, int day=0, int month=0, int year=0);
| Parameter |
Type |
Description |
| name |
String |
The name of your custom metric |
| day |
int |
if 0 you will receive data for that specific month. |
| month |
int |
if 0 you will receive data for that specific year |
| year |
int |
if 0 you will receive data for all time. |
Asynchronous Version
Data()->CustomMetric(const std::string& name, RequestDelegate targetDelegate);
Data()->CustomMetric(const std::string& name, int day, int month, int year, RequestDelegate targetDelegate);
| Parameter |
Type |
Description |
| name |
String |
The name of your custom metric |
| day |
int |
if 0 you will receive data for that specific month. |
| month |
int |
if 0 you will receive data for that specific year |
| year |
int |
if 0 you will receive data for all time. |
| targetDelegate |
RequestDelegate |
delegate method to call, you can create the delegate using fastdelegate::MakeDelegate(instance, method pointer) e.x fastdelegate::MakeDelegate(this,&MyClass::MethodName) |
gPlaytomic->Data()->CustomMetricAsync("Clicked sponsor link"e,fastdelegatee::MakeDelegate(thise,&CMyClasse::CustomMetricFinish));
voide CMyClasse::CustomMetricFinish( CPlaytomicResponsePtre& response )
{
if (response->ResponseSucceded())
{
FDatae value;
value = response->ResponseData().get("Value"e,value);
std::coute << "custom metric = "e << value.asString() << std::endle;
}
else
{
std::coute << "response failed error code = " << response->ResponseError() << std::endle;
}
}
Level metrics
There are three methods for retrieving level metrics. The functions that receive the data are different from the above examples.
Synchronous Version
Data()->LevelCounterMetric(const std::string& name, const std::string& level, int month=0, int year=0);
Data()->LevelCounterMetric(const std::string& name, const std::string& level,int day,int month, int year);
Data()->LevelCounterMetric(const std::string& name, int levelNumber, int month=0, int year=0);
Data()->LevelCounterMetric(const std::string& name, int levelNumber, int day,int month, int year);
Data()->LevelAverageMetric(const std::string& name, const std::string& level, int month=0, int year=0);
Data()->LevelAverageMetric(const std::string& name, const std::string& level, int day, int month, int year);
Data()->LevelAverageMetric(const std::string& name, int levelNumber, int month=0, int year=0);
Data()->LevelAverageMetric(const std::string& name, int levelNumber, int day,int month, int year);
Data()->LevelRangedMetric(const std::string& name, const std::string& level, int month=0, int year=0);
Data()->LevelRangedMetric(const std::string& name, const std::string& level, int day, int month, int year);
Data()->LevelRangedMetric(const std::string& name, int levelNumber, int month=0, int year=0);
Data()->LevelRangedMetric(const std::string& name, int levelNumber, int day,int month, int year);
| Parameter |
Type |
Description |
| name |
String |
The name of your custom metric |
| level |
int or String |
The level number or name |
| callback |
Function |
Your function that receives the data |
| options |
Object |
Optional object which may contain day, month and year properties |
gPlaytomic->Data()->LevelCounterMetricAsync("Started level","level 1",fastdelegate::MakeDelegate(this,&MyClass::LevelCounterFinish));
gPlaytomic->Data()->LevelRangedMetricAsync("Shots remaining","level 1",3,2010,fastdelegate::MakeDelegate(this,&MyClass::LevelRangedFinish));
gPlaytomic->Data()->LevelAverageMetricAsync("Retries","level 1",22,3,2010,fastdelegate::MakeDelegate(this,&MyClass::LevelAverageFinish));
void MyClass::LevelCounterFinish()
{
if (response->ResponseSucceded())
{
FData value;
value = response->ResponseData().get("Value",value);
std::cout << "level counter = " << value.asString() << std::endl;
}
else
{
std::cout << "response failed error code = " << response->ResponseError() << std::endl;
}
}
void MyClass::LevelRangedFinish()
{
if (response->ResponseSucceded())
{
FData value;
value = response->ResponseData().get("Value",value);
std::cout << "level ranged = " << value.asString() << std::endl;
}
else
{
std::cout << "response failed error code = " << response->ResponseError() << std::endl;
}
}
void MyClass::LevelAverageFinish()
{
if (response->ResponseSucceded())
{
FData value;
value = response->ResponseData().get("Average",value);
if(value.isInt())
std::cout << "level Average = " << value.asInt() << std::endl;
value = response->ResponseData().get("Min",value);
if(value.isInt())
std::cout << "level Min = " << value.asInt() << std::endl;
value = response->ResponseData().get("Max",value);
if(value.isInt())
std::cout << "level Max = " << value.asInt() << std::endl;
value = response->ResponseData().get("Total",value);
if(value.isInt())
std::cout << "level Total = " << value.asInt() << std::endl;
}
}
ErrorCodes
When a Playtomic service is unreachable or has an error it will return a numeric error code. This table describes those errors:
General errors
These errors may be returned from any service.
| Code |
Meaning |
| 0 |
No error |
| 1 |
General error, this typically means the player is unable to connect to the Playtomic servers |
| 2 |
Invalid game credentials. Make sure you use your SWFID and GUID from the "API" section in the dashboard. |
| 3 |
Request timed out. |
| 4 |
Invalid request. This means the request wasn't formed right, probably because the API key wasn't provided or was incorrect. |
GeoIP errors
| Code |
Meaning |
| 100 |
GeoIP API has been disabled. This may occur if your game is faulty or overwhelming the Playtomic servers. |
Leaderboard errors
| Code |
Meaning |
| 200 |
Leaderboard API has been disabled. This may occur if your game is faulty or overwhelming the Playtomic servers. |
| 201 |
The source URL or name weren't provided when saving a score. Make sure the player specifies a name and the game is initialized before anything else using the code in the "Set your game up" section. |
| 202 |
Invalid auth key. You should not see this normally, players might if they tamper with your game. |
| 203 |
No Facebook user id on a score specified as a Facebook submission. |
| 204 |
Table name wasn't specified for creating a private leaderboard. |
| 205 |
Permalink structure wasn't specified: http://website.com/game/whatever?leaderboard= |
| 206 |
Leaderboard id wasn't provided loading a private leaderboard. |
| 207 |
Invalid leaderboard id was provided for a private leaderboard. |
| 208 |
Player is banned from your leaderboard. |
| 209 |
SaveAndList only. The score was not the player's best score. You can message the player, highlight their best via the SubmittedOrBest boolean property of scores, or override this by setting allowduplicates to true. |
GameVars errors
| Code |
Meaning |
| 300 |
GameVars API has been disabled. This may occur if your game is faulty or overwhelming the Playtomic servers. |
Level sharing errors
| Code |
Meaning |
| 400 |
Level sharing API has been disabled. This may occur if your game is faulty or overwhelming the Playtomic servers. |
| 401 |
Invalid rating value (must be 1 - 10). |
| 402 |
Player has already rated that level. |
| 403 |
The level name wasn't provided when saving a level. |
| 404 |
Invalid image auth. You should not see this normally, players might if they tamper with your game. |
| 405 |
Invalid image auth (again). You should not see this normally, players might if they tamper with your game. |
| 406 |
The level already exists. This is determined via a hash of the game id, level name, player ip address and name, and source url. |
Data API errors
| Code |
Meaning |
| 500 |
Data API has been disabled. This may occur if the Data API is not enabled for your game, or your game is faulty or overwhelming the Playtomic servers. |