• Hey Guest! Ever feel like entering a Game Jam, but the time limit is always too much pressure? We get it... You lead a hectic life and dedicating 3 whole days to make a game just doesn't work for you! So, why not enter the GMC SLOW JAM? Take your time! Kick back and make your game over 4 months! Interested? Then just click here!
  • Hello [name]! Thanks for joining the GMC. Before making any posts in the Tech Support forum, can we suggest you read the forum rules? These are simple guidelines that we ask you to follow so that you can get the best help possible for your issue.

Android know player rank in leaderboard?

Ruimm

Member
Hello

Is it possible to know the rank of the player in a leaderboard for Android?
As far as I know the only way to get leaderboard information is by using the "achievement_load_leaderboard" function. But this doesn't allow you to specify anything other than the start/end positions.

Is it possible to achieve this?

Thanks
 

Stubbjax

Member
Have you even read the manual for achievement_load_leaderboard? The social asynchronous callback event returns a rank for each player along with various other data.
 

sman

Member
so you have to download all the scores (thousands? millions?) just to have the player's rank?
 

sman

Member
What scores I download are my choice, OK. So I want the player's score. How do I download it?
 

sman

Member
Yes but did he check that the answer was in the manual?
I had read the manual before asking the question and had not seen the answer. And I still don't see it.
 

rIKmAN

Member
Yes but did he check that the answer was in the manual?
I had read the manual before asking the question and had not seen the answer. And I still don't see it.
Then I suggest you open your eyes and try reading it again.

It's not his job to check the manual for you, that's something you should do yourself.
If you had read the manual you would have seen the code example that shows how to get the score (amongst other things).

There is a line in the extended example which specifically references the score:
Code:
global.playerscore[i] = ds_map_find_value(async_load, "Score" + string(i));
If you can't get it working or are having trouble after you have tried to get it working based of the manual and example code then post back here and people will help, but at least put in a bit of effort in yourself first
 
Last edited:

sman

Member
That's any player. I'm talking about the player. The owner of the game.
And I suppose Ruimm was also talking about the owner of the game.
 
That's any player. I'm talking about the player. The owner of the game.
And I suppose Ruimm was also talking about the owner of the game.
I know this is old, but to get the owner of the game's info, there are instructions in the documentation:
Extended Example:
The following code would probably be called right at the startof your game, or from a special button object that you have placed in a room, and will request that the player logs into their Google Game Services or Apple Game center account:

Code:
achievement_login();
This will send off a request for the current player information and generate an asynchronous callback with the special async_load ds_map containing the following data:

Code:
var ident = ds_map_find_value(async_load, "id");
if ident == achievement_our_info
   {
   var name = ds_map_find_value(async_load, "name" );
   var playerid = ds_map_find_value(async_load, "playerid" );
   global.OurName = name;
   global.OurId = playerid;
   if os_type == os_android
      {
      achievement_load_leaderboard("CgkIs9_51u0PEAIQBw", 1, 100, achievement_filter_friends_only);
      }
   else
      {
      achievement_load_leaderboard("leaderboard1id", 1, 100, achievement_filter_friends_only);
      }
   achievement_get_challenges();
   achievement_load_friends();
   achievement_load_progress();
   }
The above code checks the returned ds_map in the Social Asynchronous Event and if its "id" matches the constant being checked, it then loops through the map storing all the different values in variables before calling the rest of the functions to prepare the leaderboards, challenges and friends lists.
 
P

Panagis Alisandratos

Guest
It looks like the only way to get THE player's (currently logged-in user's) ranking would be to call achievement_load_leaderboard, loop through the payload while checking for an id that matches the player's id, and repeat this process with the next page (set of upper & lower indices) until the player's rank is found. Is there a better way?

Also, why are some of you mean to @sman? As far as I can tell, he's right when he says the manual does not explicitly state how to get the ranking of the currently logged-in user.
 
Last edited by a moderator:
L

Lonewolff

Guest
Am I the only one who is confused as to why the manual references Android all of the place and then says

NOTE: this function does not work on the Android platform.
I'm staring at the manual in the latest stable release of GMS 1.4 right this second.
 
P

Panagis Alisandratos

Guest
It looks like the proper way to deal with this would be to load the player's id through GM functions and then use it to make a request to the endpoint @FrostyCat posted. If you don't want to write your own extension, try out the following code based on my previous post:

DISCLAIMER
This code has not been tested.


Create Event, Room Creation Code, or equivalent
Code:
#macro LEADERBOARD_ID_IOS = "iosBoardOfLeaders"
#macro LEADERBOARD_ID_ANDROID = "androidBoardOfLeaders"
#macro LEADERBOARD_ENTRIES_INCREMENT 100

global.leaderboard_entries_start = 1;
global.high_score = 0;

achievement_login();
if (achievement_available()) {
  load_leaderboard();
}
load_leaderboard()
Code:
// On iOS, a Game Center user is considered to be friends with him/herself, how nice.
achievement_load_leaderboard(
  os_type == os_ios ? LEADERBOARD_ID_IOS : LEADERBOARD_ID_ANDROID,
  global.leaderboard_entries_start,
  global.leaderboard_entries_start - 1 + LEADERBOARD_ENTRIES_INCREMENT,
  achievement_filter_friends_only
);
Async - Social
Code:
var ident = async_load{? "id"];

switch (ident) {
  case achievement_our_info:
    global.player_id = async_load[? "playerid"];
    break;
  case achievement_leaderboard_info:
    var numentries = async_load[? "numentries"];
    if (numentries > 0) {
      for (var i = 0; i < numentries; i++) {
        var index = string(i);
        if (global.player_id = async_load[? "Playerid" + index]) {
          global.high_score = async_load[? "Score" + index];
          break;
        }
      }
      if (i == numentries) {
        global.leaderboard_entries_start += LEADERBOARD_ENTRIES_INCREMENT;
        load_leaderboard();
      }
    }
    break;
}
This method is only worthwhile if the player is not the most popular human being on the face of the earth and, therefore, does not have a friends list that is as large as the unfiltered list of players. Otherwise, this method will take an unacceptable amount of time to complete.
 
P

Panagis Alisandratos

Guest
Alrighty, got something working on Android and iOS :banana:.

Android

Why write another extension when you already need to include the Google Play Services Extension for leaderboards to work?

1. To fetch the current player's rank, include the following in <PathToYourGame'sProjectRoot>/extensions/GooglePlayServicesExtension/AndroidSource/Java/
GooglePlayServicesExtension.java

Code:
// place these imports where most appropriate to keep things organized
import com.google.android.gms.common.api.ResultCallback;
import com.google.android.gms.games.GamesStatusCodes;
import com.google.android.gms.games.leaderboard.LeaderboardVariant;
import com.google.android.gms.games.leaderboard.Leaderboards;

public class GooglePlayServicesExtension ...
  ...
  public void getPlayerRank(String leaderboardId) {
    // Leaderboards is deprecated, but then again, so is this godforsaken extension
    Games.Leaderboards.loadCurrentPlayerLeaderboardScore(
      getApiClient(),
      leaderboardId,
      LeaderboardVariant.TIME_SPAN_ALL_TIME,
      LeaderboardVariant.COLLECTION_PUBLIC
    ).setResultCallback(new ResultCallback<Leaderboards.LoadPlayerScoreResult>() {
      @Override
      public void onResult(final Leaderboards.LoadPlayerScoreResult result) {
          int dsMapIndex = RunnerJNILib.jCreateDsMap(null, null, null);
        if (
          result != null &&
          GamesStatusCodes.STATUS_OK == result.getStatus().getStatusCode() &&
          result.getScore() != null
        ) {
          // valid result
          RunnerJNILib.DsMapAddDouble(dsMapIndex, "type", 700);
          RunnerJNILib.DsMapAddDouble(dsMapIndex, "rank", result.getScore().getDisplayRank());
        }
        else {
          RunnerJNILib.DsMapAddDouble(dsMapIndex, "type", 701);
        }
        RunnerJNILib.CreateAsynEventWithDSMap(dsMapIndex, EVENT_OTHER_SOCIAL);
      }
    });
  }
  ...
}
Check out LeaderboardScore for a list of available methods to call instead of getDisplayRank (e.g. to get the player's score: result.getScore().getRawScore()).

2. Update GooglePlayServicesExtension.ext from within GameMaker by mapping getPlayerRank to a function that takes a single argument of type string. Name it something along the lines of get_player_rank_gps.

If you are not also including the iOS extension (see iOS section), add 2 macros:
LBPlayerRank: 700 and LBFailedToGetRank: 701.

Screen Shot 2018-02-05 at 2.37.19 PM.png
Updating GooglePlayServicesExtension.ext with function to get current player's score. As explained earlier, you are not limited to fetching a ranking. For my current project, I am only interested in the score. TYPO: 'gms' should be 'gps'.

iOS

1. Create an extension through GameMaker with a function mapped to getPlayerRank (from code below) that takes a single argument of type string. Name it something along the lines of get_player_rank_ios.

2. Add LeaderboardBridge as the Class Name in the extension iOS properties (accessed through Extra Platforms).

3. Add 2 macros: LBPlayerRank: 700 and LBFailedToGetRank: 701.

4. Add the following source files:

LeaderboardBridge.h
Code:
#import <Foundation/Foundation.h>

@interface LeaderboardBridge : NSObject
{
}

- (void) getPlayerRank:(char *)leaderboardId;
@end
LeaderboardBridge.mm
Code:
#import "LeaderboardBridge.h"
#include <asl.h>
#include <stdio.h>
#include <GameKit/GameKit.h>

@implementation LeaderboardBridge

const int EVENT_OTHER_SOCIAL = 70;
extern UIView *g_glView;

extern "C" NSString* findOption( const char* _key );
extern bool F_DsMapAdd_Internal(int _index, char* _pKey, double _value);
extern bool F_DsMapAdd_Internal(int _index, char* _pKey, char* _pValue);
extern int CreateDsMap( int _num, ... );
extern void CreateAsynEventWithDSMap(int dsmapindex, int event_index);

enum {
    LBPlayerRank = 700,
    LBFailedToGetRank = 701,
};

- (void) getPlayerRank:(char *)leaderboardId {
    NSLog(@"Getting local player rank");
    int dsMapIndex = CreateDsMap(0);
    GKLeaderboard *leaderboardRequest = [[GKLeaderboard alloc] init];
    leaderboardRequest.identifier = [NSString stringWithFormat:@"%s", leaderboardId];
    if (leaderboardRequest != nil) {
        [leaderboardRequest loadScoresWithCompletionHandler:^(NSArray *scores, NSError *error) {
            if (error != nil) {
                NSLog(@"%@", error);
                F_DsMapAdd_Internal(dsMapIndex, (char *)"type", LBFailedToGetRank);
            }
            else {
                F_DsMapAdd_Internal(dsMapIndex, (char *)"type", LBPlayerRank);
                F_DsMapAdd_Internal(dsMapIndex, (char *)"rank", (int)leaderboardRequest.localPlayerScore.rank);
            }
            CreateAsynEventWithDSMap(dsMapIndex, EVENT_OTHER_SOCIAL);
        }];
    }
    else {
        F_DsMapAdd_Internal(dsMapIndex, (char *)"type", LBFailedToGetRank);
        CreateAsynEventWithDSMap(dsMapIndex, EVENT_OTHER_SOCIAL);
    }
}

@end
Checkout GKScore for a list of available properties to use instead of rank (e.g. to get the player's score: localPlayerScore.value).

GameMaker

Some synchronous event, like Create
Code:
#macro LEADERBOARD_ID_IOS "your_ios_leaderboard_id"
#macro LEADERBOARD_ID_ANDROID "your_gps_leaderboard_id"

switch (os_type) {
  case os_ios:
    get_player_rank_ios(LEADERBOARD_ID_IOS);
    break;
  case os_android:
    get_player_rank_gps(LEADERBOARD_ID_ANDROID);
    break;
}
Async Social Event
Code:
var type = async_load[? "type"];
  switch (type) {
    case LBPlayerRank: // 700
      show_debug_message("PLAYER RANK: " + string(async_load[? "rank"]));
      break;
    case LBFailedToGetRank: // 701
      show_debug_message("failed to get rank");
      // cope with your failure
      break;
  }

Notes
  • I've arbitrarily chosen 700 and 701 to serve as asynchronous identifiers.
    • The corresponding macros are optional and can be substituted with the integer literals.
  • The code snippets have been adapted slightly to fetch rankings instead of scores.
  • iOS code is being used on a production app (check my signature if interested).
  • Android code has been tested locally.
 
Top