Author Topic: I've played with Steam API and here are results.. achievements for Your game ;-)  (Read 2555 times)

Offline dreamerman

  • Global Moderator
  • Dr. Type
  • *******
  • Posts: 394
    • View Profile
    • my personal website
Current version:10.11.2021

This is my attempt to use Steam API or rather it's user stats and achievements part in GLbasic.
Yeah, it isn't clean code, not bullet-proof, and have some issues and it's under constant development so it will fit my needs. Most important thing is that, user stats and achievements are working with it, tested on live app.

Download consist of two files:
steam_sdk.gbas -> imports for proper Steam API functions from dll, only part of functions were tested, core stuff is working - RunFrame()
stats_manager.gbas -> Stat Manager type/object, main thing simplify using Steam stuff in game

Stats Manager supports int, float user stats, achievements and will take care of loading info from Steam, and with use of main loop uploading them and unlocking achievements.
Next possble feature are leaderboards.


Take note, that if You will run project from Steam version GLBasic editor it will inherit its app_id, so You want be able to test it properly, not sure if putting 'steam_appid.txt' into project exe path should fix that, but You may need to either grab STDOUT to file or use alternative editor/older version.
Also read this: https://partner.steamgames.com/doc/features/achievements
Some example template to give Your an idea how to use it:

Code: (glbasic) [Select]
GLOBAL mySteam AS stats_manager_object, i1%
mySteam.Init(1)      // after Init this will also request FOR current stats from Steam
i1% = mySteam.addAchievement("achiev_10_games_played")
mySteam.addUserStat("games_played", 10, 0, 0, i1%)
i1% = mySteam.addAchievement("achiev_10000_enemies_destroyed")
mySteam.addUserStat("enemies_destroyed", 10000, 0, 1, i1%)   // frequently changed value
// requestCurrentStats() was called internally in Init()
SETLOOPSUB "GLB_ON_LOOP" // set main loop FOR app
END   // exit app

// GLB_ON LOOPS
SUB GLB_ON_LOOP:
  mySteam.RunFrame()
   // after game played
   IF (match_finished) THEN mySteam.updateUserStat(-1, "games_played", 1, 1)   // increment by 1
   // OR you can do it manually
   IF (enemy_destroyed)
     INC mySteam.userstat_list[1].value_curr_int%
   ENDIF
   // IF You changed value manually THEN update it once per second - when calculation FPS
   IF last_fps_time - GETTIMERALL() >= 1000
     mySteam.updateUserStat(1, "", 0, 0) // update destroyed enemy counter
     // call only IF an achievement was unlocked - TO show it
     IF mySteam.status.achievement_was_unlocked% = 1 THEN mySteam.storeUserStats(0)
   ENDIF 
  IF KEY(01) THEN END // IF escape KEY THEN END app
ENDSUB
//called on pausing AND before app exit
SUB GLB_ON_PAUSE:
// you need TO have them both AS RunFrame will call them when user open overlay
ENDSUB
// called on restoring app
SUB GLB_ON_RESUME:
ENDSUB
// called before app axit
SUB GLB_ON_QUIT:
  mySteam.Shoutdown()
ENDSUB

Steps to add achievements:
Remember first to add achievements to Your game in Steam app admin panel (not store admin - app store page, don't turn Achievement store feature before implementing and testing them).
Use Steam stat/achievement name in code, but field ID number is internal to Steam and in app You must use id number based on array position.
« Last Edit: 2021-Nov-10 by dreamerman »
Check my source code editor for GLBasic - link Update: 20.04.2020

Offline spacefractal

  • Community Developer
  • Prof. Inline
  • ******
  • Posts: 3955
    • View Profile
    • Space Fractal
Re: Anyone played with Steam API?
« Reply #1 on: 2019-Apr-03 »
first time was due the compiler glbasic was used was simply just...... too old... but howover yes you are right as archivements, mostly in Genius Greedy Mouse (which have bounch of internal once) should do have them.

The compilers is updated and should been quite easy to integrate the steam SDK directly into the source code im should do this a day.
Genius.Greedy Mouse - Karma Miwa - Spot Race - CatchOut - PowerUp Elevation and The beagle Jam.

Offline dreamerman

  • Global Moderator
  • Dr. Type
  • *******
  • Posts: 394
    • View Profile
    • my personal website
Re: Anyone played with Steam API?
« Reply #2 on: 2019-Apr-03 »
Yeah, I wasn't sure but I though that Genius Greedy Mouse had some kind of 'achievements'.. I know that game should defend itself by quality, but now even small games have achievements, and some users just skip games without them with filters or just thinking that game isn't finished or due other reasons.
That's really nice to hear that such integration (into GLB source code) should be now easier to implement. As ability to call Steam API in such simple way (at least those basic things like stats, achievements, maybe leaderboards) would be awesome and that could be another great feature to list on Steam product page.
Check my source code editor for GLBasic - link Update: 20.04.2020

Online MrPlow

  • Prof. Inline
  • *****
  • Posts: 1784
    • View Profile
Re: Anyone played with Steam API?
« Reply #3 on: 2019-Jul-05 »
Achievements would be great to have for Steam - is this planned soon for a future update?
Comp:
Speccy-48k, Speccy-128k, Amigas, PCs

Offline bigsofty

  • Community Developer
  • Prof. Inline
  • ******
  • Posts: 2747
    • View Profile
Re: Anyone played with Steam API?
« Reply #4 on: 2019-Jul-05 »
Yup, access to the Steam API in general would be very handy.

Nice bit of inlining there BTW Dreamerman!
Cheers,

Ian.

“It is practically impossible to teach good programming style to students that have had prior exposure to BASIC.  As potential programmers, they are mentally mutilated beyond hope of regeneration.”
(E. W. Dijkstra)

Offline spacefractal

  • Community Developer
  • Prof. Inline
  • ******
  • Posts: 3955
    • View Profile
    • Space Fractal
Re: Anyone played with Steam API?
« Reply #5 on: 2019-Jul-08 »
Yeah, I wasn't sure but I though that Genius Greedy Mouse had some kind of 'achievements'.. I know that game should defend itself by quality, but now even small games have achievements, and some users just skip games without them with filters or just thinking that game isn't finished or due other reasons.
That's really nice to hear that such integration (into GLB source code) should be now easier to implement. As ability to call Steam API in such simple way (at least those basic things like stats, achievements, maybe leaderboards) would be awesome and that could be another great feature to list on Steam product page.

When im did that game for some years ago, the glbasic compilers used was too old, so not include 'achievements' into source code. So im skipped it, but the game was also releaseed when its was easier to use trading cards (which im today kindy as, due there is too much abuse). So no im diddent include the sdk at all, but its should been possible today since compilers for the steam version is updated. Im did do support cloud support as its did not require to use SDK at all.
Genius.Greedy Mouse - Karma Miwa - Spot Race - CatchOut - PowerUp Elevation and The beagle Jam.

Offline dreamerman

  • Global Moderator
  • Dr. Type
  • *******
  • Posts: 394
    • View Profile
    • my personal website
Re: Anyone played with Steam API?
« Reply #6 on: 2020-Apr-10 »
Hello, so much time passed, and I'm curious if anyone played with Steam SDK during that time. Because of that I'm bumping this topic, but that's not only reason.
Lately I was checking whats changed with this, one thing updating GCC used by GLBasic helped with some stuff, another thing that I noticed is very important update in Steam SDK - one of previous problems was Callback system, now Valve allows to use 'manual dispatch API' that lets to omit callback's completely. Still there are other issues, so shorting a long story: GLB uses GCC compiler, Steam_api.dll (and internal steam_client.dll) is compiled with MSVC this may cause some problems. I really don't like such messing with C++, all those standards, conventions, pointers, eh..
So really narrowing it down, thanks to all above we can include Steam API headers in GLB projects like this:

Code: (glbasic) [Select]
// some Steam SDK playing around

// put 'Steam_SDK\redistributable_bin\steam_api.lib' to '\public' and 'steam_api.dll' to project .app folder where exe is
// add those commands to project options
// cmp -I"your_Steam_SDK_path\public\steam"
// lnk -L"your_Steam_SDK_path\public" -lsteam_api

// put below stuff in separate file
// in main source file just call Steam_Test()

INLINE
// why not to add this
//#include "iostream" // this is causing some errors now, in previous Steam SDK it was needed?
#include "stdio.h"
#include "stdint.h"
#include "assert.h"

// trick to add VS style nullptr to older GCC versions
// Scott Meyers. More Effective C++ 1996
// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2431.pdf
const                        // this is a const object...
class {
public:
  template<class T>          // convertible to any type
    operator T*() const      // of null non-member
    { return 0; }            // pointer...
  template<class C, class T> // or any type of null
    operator T C::*() const  // member pointer...
    { return 0; }
private:
  void operator&() const;    // whose address can't be taken
} nullptr = {};

// current SteamSDK uses 'static_assert' macros in some functions
// trick to add static_assert support on older C/C++ standards (as GLB core wont compile properly on C++11)
// Joseph Quinsey : https://stackoverflow.com/questions/54357949/static-assert-for-c90-on-gcc
#define TOKENPASTE(a, b) a ## b // "##" is the "Token Pasting Operator"
#define TOKENPASTE2(a,b) TOKENPASTE(a, b) // expand then paste
#define static_assert(x, msg) enum { TOKENPASTE2(ASSERT_line_,__LINE__) = 1 / (msg && (x)) }


// and here goes Steam stuff
#include "steam_api.h"
// yeap that's all, now you can use it ;-)


//CSteamID your_uid; // some dummy test
ENDINLINE



FUNCTION Steam_Test%:
LOCAL init_ok%, user_lvl%, app_id%, owner_uid%, your_uid%, friends_count%
LOCAL your_name$, some_name$, test_str$
INLINE
init_ok = SteamAPI_Init();
user_lvl = SteamUser()->GetPlayerSteamLevel();
your_name_Str = SteamFriends()->GetPersonaName();
app_id = SteamUtils()->GetAppID(); // this will give GLBasic appid, as app runs from it, using it from standalone exe will give proper ID
CSteamID steamID;
AccountID_t taccountid;
steamID = SteamApps()->GetAppOwner(); // get CSteamID struct
owner_uid = steamID.ConvertToUint64(); // this should be Your SteamID3 ?? but for some reason it isn't or maybe I'm missing something
//taccountid = steamID.GetAccountID(); // unsigned int
//owner_uid = int(taccountid); // this also doesn't give proper Steam ID

// problems starts here
steamID = SteamUser()->GetSteamID(); // sometime this causes Access Violation Exception :-((
//taccountid = steamID.GetAccountID();
//your_uid = taccountid;
your_uid = steamID.ConvertToUint64(); // this should give your proper SteamID3
// both owner_uid and your_uid should be same, yet they differs, maybe some binary Flags are added that's not mentioned in docs

// even more mess, this is copied from official doc
int nFriends = SteamFriends()->GetFriendCount( k_EFriendFlagImmediate );
if ( nFriends == -1) nFriends = 0;
friends_count = nFriends; // this works
for ( int i = 0; i < nFriends; ++i )
{
CSteamID friendSteamID;
//friendSteamID = SteamFriends()->GetFriendByIndex( i, k_EFriendFlagImmediate ); // this will cause Access Violation Exception
//some_name_Str = SteamFriends()->GetFriendPersonaName( friendSteamID );
}


// end using Steam API
SteamAPI_Shutdown();
ENDINLINE
DEBUG "proper init: " + init_ok% + ", your lvl: " + (user_lvl%) + ", you name: '" + (your_name$) + "'"
DEBUG ", You have: " + (friends_count%) + " friends on Steam, some name: '" + some_name$ + "', your_id: " + your_uid%
DEBUG ", app_id: " + app_id% + ", owner_id: " + owner_uid% + "\n"
// if that debug was in one line then some variables get mesed up - like user_lvl%

ENDFUNCTION

Remember to put steam_api.lib & .dll to proper directories, above code is another concept how it could look when current issues would be resolved.
Using some stuff causes 'Access Violation Exceptions', some other things doesn't work properly from what I see and I'm not sure why, I'm just C++ peasant ;-)
With this method one important thing matters, You need to use Steam GLB version (>16) as it has newer GCC compiler. Probably some tricks could help with above mentioned problems, but it's just nah.. for me :/

Method showed in my first post - using Steam API functions through 'DECLARE' is still a solution, it's working with older GLB versions, it has some other issues but if achievements stuff would work it would be fine for me..


EDIT: my initial post - just to keep it in history:

I'm curious if anyone tried to make use of Steam API, for achievements, leaderboards or whatever.
As during fast looking at GLBasic made games on Steam I didn't see any project that support those features. Most notable would be support for achievements.

Some basic stuff can be done with INLINE, using DECLARE to call steam_api.dll functions.
main.gbas
Code: (glbasic) [Select]
DEBUG "False: " + FALSE + ", True: " + TRUE + "\n"
DEBUG "Steam init: " + SAPI_SteamAPI_init() + "\n"
SAPI_SteamAPI_Shutdown()
steam_min.gbas
Code: (glbasic) [Select]
INLINE
        }
                extern "C" {
                        #include <cstdio>
                       
                       
                }
        namespace __GLBASIC__ {
       
typedef bool _Bool;
typedef unsigned char uint8;
typedef signed char int8;
typedef short int16;
typedef unsigned short uint16;
typedef int int32;

const char* STEAMUSER_INTERFACE_VERSION = "SteamUser019";
       
typedef int32 HSteamPipe;
typedef int32 HSteamUser;
typedef int32 ISteamUser;


        DECLARE(SteamAPI_Init, "steam_api.dll", (), bool);
DECLARE(SteamAPI_Shutdown, "steam_api.dll", (), void);
DECLARE(SteamClient, "steam_api.dll", (), intptr_t);

        DECLARE(SteamAPI_ISteamClient_CreateSteamPipe, "steam_api.dll", (intptr_t), HSteamPipe);
        DECLARE(SteamAPI_ISteamClient_BReleaseSteamPipe, "steam_api.dll", (intptr_t, HSteamPipe), bool);
        DECLARE(SteamAPI_ISteamClient_ConnectToGlobalUser, "steam_api.dll", (intptr_t, HSteamPipe), HSteamUser);
DECLARE(SteamAPI_ISteamClient_GetISteamUser, "steam_api.dll", (intptr_t, HSteamUser, HSteamPipe, const char *), intptr_t);

DECLARE(SteamAPI_ISteamUser_GetPlayerSteamLevel, "steam_api.dll", (intptr_t), int);

ENDINLINE


FUNCTION SAPI_SteamAPI_init%:
LOCAL ret%, user_lvl%
INLINE
ret = SteamAPI_Init();
intptr_t sclient;
        sclient = SteamClient();
        HSteamPipe hsteampipe;
HSteamUser hsteamuser;
hsteampipe = SteamAPI_ISteamClient_CreateSteamPipe(sclient);
hsteamuser = SteamAPI_ISteamClient_ConnectToGlobalUser(sclient, hsteampipe);

intptr_t isteamuser;
isteamuser = SteamAPI_ISteamClient_GetISteamUser(sclient, hsteamuser, hsteampipe, STEAMUSER_INTERFACE_VERSION);;

user_lvl = SteamAPI_ISteamUser_GetPlayerSteamLevel(isteamuser);
SteamAPI_ISteamClient_BReleaseSteamPipe(sclient, hsteampipe);

ENDINLINE
DEBUG "your lvl: " + user_lvl% + "\n"
RETURN ret%
ENDFUNCTION

FUNCTION SAPI_SteamAPI_Shutdown%:
INLINE
SteamAPI_Shutdown();
ENDINLINE
ENDFUNCTION
Put 'steam_api.dll' to project exe folder, and create 'steam_appid.txt' containing your app_id to make this work. And result is that app will print in Debug Your (currently logged in) Steam Level (that from crafting game badges)..

Only issue with this, I have no idea how to make callback's from those functions (yet from what I saw, some other language ports just ignore this problem), many of them works asynchronously, and after completed they invoke/call some callback function, like this, you request for user stats, when Steam internal function get current stats, it callback to some function that normally is in project source. Can this be done with EXPORT, or are some other tricks to do this.
Including whole Steam API with 'steam_api_flat.h' header also can be some solution but proper including/linking is currently out of my scope :D Help of some C++ expert is needed here :P
« Last Edit: 2020-Apr-23 by dreamerman »
Check my source code editor for GLBasic - link Update: 20.04.2020

Offline dreamerman

  • Global Moderator
  • Dr. Type
  • *******
  • Posts: 394
    • View Profile
    • my personal website
In order to keep all things in one thread and keep it clean I will edit my first post with latest info, for archive reasons I've copied my initial post to my previous response.

And here is todays update.
As I'm getting closer with larger update to my Steam game, I was looking on few things that were hard to implement earlier, one of them were Steam Achievements.
Making it short, there are two ways to use Steam API: a) including it as C++ headers and using directly in inline or b) calling functions that are exported from dll.
a) may be more clean looking, yet it needs latest GLB version, and I still have some issues with either wrong calling convention, pointers or whatever.
b) could be more limited as probably not all functions can be used this way, but still it's a good solution.
After trying to get a) work I turned into option b) and.. Ehm it was supposed to be short ;-) Soo.. I made some help functions and here it is, an 'easy' way to add Steam Achievements to Your game.
Yeah, it isn't clean code, not bullet-proof, and have some issues and it's under constant development so it will fit my needs. Most important thing is that, user stats and achievements are working with it, tested on live app.

Take note, that if You will run project from Steam version GLBasic editor it will inherit its app_id, so You want be able to test it properly, not sure if putting 'steam_appid.txt' into project exe path should fix that, but You may need to either grab STDOUT to file or use alternative editor/older version.
Also read this: https://partner.steamgames.com/doc/features/achievements
Some example template to give Your an idea how to use it:

Code: (glbasic) [Select]
GLOBAL mySteam AS stats_manager_object, i1%
mySteam.Init(1)      // after Init this will also request FOR current stats from Steam
i1% = mySteam.addAchievement("achiev_10_games_played")
mySteam.addUserStat("games_played", 10, 0, 0, i1%)
i1% = mySteam.addAchievement("achiev_10000_enemies_destroyed")
mySteam.addUserStat("enemies_destroyed", 10000, 0, 1, i1%)   // frequently changed value
// requestCurrentStats() was called internally in Init()
SETLOOPSUB "GLB_ON_LOOP" // set main loop FOR app
END   // exit app

// GLB_ON LOOPS
SUB GLB_ON_LOOP:
  mySteam.RunFrame()
   // after game played
   IF (match_finished) THEN mySteam.updateUserStat(-1, "games_played", 1, 1)   // increment by 1
   // OR you can do it manually
   IF (enemy_destroyed)
     INC mySteam.userstat_list[1].value_curr_int%
   ENDIF
   // IF You changed value manually THEN update it once per second - when calculation FPS
   IF last_fps_time - GETTIMERALL() >= 1000
     mySteam.updateUserStat(1, "", 0, 0) // update destroyed enemy counter
     // call only IF an achievement was unlocked - TO show it
     IF mySteam.status.achievement_was_unlocked% = 1 THEN mySteam.storeUserStats(0)
   ENDIF 
  IF KEY(01) THEN END // IF escape KEY THEN END app
ENDSUB
called on pausing AND before app exit
SUB GLB_ON_PAUSE:
// you need TO have them both AS RunFrame will call them when user open overlay
ENDSUB
// called on restoring app
SUB GLB_ON_RESUME:
ENDSUB
// called before app axit
SUB GLB_ON_QUIT:
  mySteam.Shoutdown()
ENDSUB


ps. download link in first post.
ps2. I rushed this a little just to post it today.
Check my source code editor for GLBasic - link Update: 20.04.2020

Offline spacefractal

  • Community Developer
  • Prof. Inline
  • ******
  • Posts: 3955
    • View Profile
    • Space Fractal
I'd want me to clean up posts let me know.
« Last Edit: 2020-Apr-23 by spacefractal »
Genius.Greedy Mouse - Karma Miwa - Spot Race - CatchOut - PowerUp Elevation and The beagle Jam.

Online MrPlow

  • Prof. Inline
  • *****
  • Posts: 1784
    • View Profile
Wow! Sounds really promising...steam api functionality!!
Comp:
Speccy-48k, Speccy-128k, Amigas, PCs

Offline dreamerman

  • Global Moderator
  • Dr. Type
  • *******
  • Posts: 394
    • View Profile
    • my personal website
@spacefractal
Thanks, but there is no need for deleting previous posts, I like to have all of them for tracking and archive reasons ;-)

@MrPlow
Generally whole Steam API can be used, yet this project will be rather limited to stats/achievements part and some basic utility functions that are needed to have those features. Currently I'm not even bothering with 'Avarage' type of UserStats, next thing to implement would be leaderboards, and this also doesn't seem to be difficult. Core stuff like RunFrame() functions can be used to add other features if someone needs, both Callback's and CallResult's responses seems to work properly so there should be no problem. I will try to keep Stats_manager type/object simple and fairly clean.

Currently using dll's exported function through Declare seems more reliable to me, so for now I will stick to it. Possible test with native C++ interfaces may be done in future, yet as this solution is sufficient for me (at least for know) I have no need to push on that. If someone needs, additional functions can be easily added, for example to launch web-browser in Steam overlay just this:
Code: (glbasic) [Select]
Steam_API_SteamFriends_ActivateGameOverlayToWebPage("https://www.glbasic.com/forum/index.php")
Check my source code editor for GLBasic - link Update: 20.04.2020

Offline spacefractal

  • Community Developer
  • Prof. Inline
  • ******
  • Posts: 3955
    • View Profile
    • Space Fractal
you can dw the source code for real implematin, but this works well as we can only currectly uses for Windows anyway. O Its was to easier track like im also cleaned up. but im wont touch any posts, except if you pm me.

Im should finally looks with archivements for Greedy Mouse as that game internally allready have them.....
Genius.Greedy Mouse - Karma Miwa - Spot Race - CatchOut - PowerUp Elevation and The beagle Jam.

Offline SnooPI

  • Dr. Type
  • ****
  • Posts: 415
    • View Profile
It's very interesting Dreamerman.
One day I will have to take a closer look.

Offline dreamerman

  • Global Moderator
  • Dr. Type
  • *******
  • Posts: 394
    • View Profile
    • my personal website
I'm using this dll method from more than year in my game for user stats/achievements and they are working without issues (at least didn't notice any and no user complained about that) so I think that's good enough for a workaround method :]
This update comes with many added functions and handlers for Steam API callbacks, but only those that most likely will be used in any GLB made game. Some changes in core functions, but most important is leader boards support added, partially tested, due to how it works, I will verify everything after adding Steam Leaderboards/Highscore system in my next project.

Module updated to use latest 1.52 API interfaces, generally most changes in Steam API were made to add support for Steam Deck. About that thing, controller support in GLB is good out of box (even get controller names), vibration can be added with SDL inline so I don't see reason to bother with Steam_Input interface for now, specially that I don't have any gamepad/controller at moment :/
Check my source code editor for GLBasic - link Update: 20.04.2020