Has anyone made a module for Steamworks?

Monkey Programming Forums/User Modules/Has anyone made a module for Steamworks?

Oddball(Posted 2013) [#1]
I'm wondering if anyone has already made a Steamworks module?

If not can someone point me in the direction of a tutorial for making a user module of a C library?

Thanks.


skid(Posted 2013) [#2]
My steamstub project should run fine on GLFW targets, you need to be signed up / approved by Steam first, which I assume / hope is a lot easier than it use to be.


Oddball(Posted 2013) [#3]
I'm trying to work out if it'd be feasible to port my game, which is already on Steam, to monkey. I use your Steamstub module for BlitzMax, how would I get that running in monkey? Sorry if that's really obvious, but I'm still getting to grips with monkey.


Why0Why(Posted 2013) [#4]
Are you going to port Hack,Slash to mobile?


Oddball(Posted 2013) [#5]
Maybe. It all depends if porting it to monkey is feasible.


Skn3(Posted 2013) [#6]
yes please port hackslashloot!!!!

There really isn't a decent (imho) game of that nature on iOS. Tried a few and none of them gave me as much fun as yours did!


Raul(Posted 2013) [#7]
@skid: where is your steamsub project?


skid(Posted 2013) [#8]
OK,

HowTo for running Steam Monkey GLFW, done with MonkeyPro72A

1. clone %monkey%/targets/glfw folder and call it steamglfw

2. inside steamglfw folder edit TARGET.MONKEY changing name to:

format_code('
#TARGET_NAME="Steam GLFW Game"
')

3. Copy the steamstub_api.lib file into the template/vc2010 folder

4. Copy the public steam include files ( steamtypes.h etc. ) into a steamglfw/template/steam folder

5. load the steamglfw/template/vs2010/MonkeyGame.sln file into Visual Studio 2010, right button on the MonkeyGame project in the Solution Explorer and select properties->Configuration Properties->Linker->Input and add steam_api.lib; to start of Additional Dependencies list for both Debug and Release

6. save, exit, set up the following code in a new folder somewhere, build and run with the target set to the new Steam GLFW Game and you should be able to connect and see some status failures to do with GetStat failing on unitialised variable.

I can finish off implementation if anyone gets this far and wants more.


steamtest.monkey:

format_codebox('
' steam demo has appid 480

Import "steamstub.cpp"

Extern

Function OpenSteam%(gameid%)
Function ReadSteam$()
Function GetSteamStat%(id$)
Function SetSteamStat(id$,value%)
Function GetSteamAchievement%(id$)
Function SetSteamAchievement(id$)
Function StoreSteamStats()
Function FindNumberOfCurrentPlayers()
Function FindOrCreateLeaderboard( name$, sortmethod%, displaytype% )
Function FindLeaderboard( name$ )
Function UploadLeaderboardScore( sendmethod%, score%, details$ )
Function DownloadLeaderboardEntries( request%, start%, finish% )

Public

' ELeaderboardDataRequest
' type of data request, when downloading leaderboard entries

Const k_ELeaderboardDataRequestGlobal = 0
Const k_ELeaderboardDataRequestGlobalAroundUser = 1
Const k_ELeaderboardDataRequestFriends = 2

' ELeaderboardSortMethod
' the sort order of a leaderboard

Const k_ELeaderboardSortMethodNone = 0
Const k_ELeaderboardSortMethodAscending = 1 ' top-score is lowest number
Const k_ELeaderboardSortMethodDescending = 2 ' top-score is highest number

' ELeaderboardDisplayType
' the display type (used by the Steam Community web site) For a leaderboard

Const k_ELeaderboardDisplayTypeNone = 0
Const k_ELeaderboardDisplayTypeNumeric = 1 ' simple numerical score
Const k_ELeaderboardDisplayTypeTimeSeconds = 2 ' the score represents a time, in seconds
Const k_ELeaderboardDisplayTypeTimeMilliSeconds = 3 ' the score represents a time, in milliseconds

' ELeaderboardUploadScoreMethod

Const k_ELeaderboardUploadScoreMethodNone = 0
Const k_ELeaderboardUploadScoreMethodKeepBest = 1
Const k_ELeaderboardUploadScoreMethodForceUpdate = 2

Global needscores=True

Function steamtest()

Local steamstate=OpenSteam(480)

If steamstate=-1

Print "Steam must be running to play this game."
Print "OpenSteam() failed."

Return

EndIf

If steamstate=1
Print "Steam is online, getting stats"

' SetSteamStat "bogeys",0
' GetSteamStat "bogeys"

FindNumberOfCurrentPlayers()

' FindLeaderboard("Lead1")
' FindOrCreateLeaderboard("Lead1",1,k_ELeaderboardDisplayTypeNumeric)
' DownloadLeaderboardEntries(k_ELeaderboardDataRequestGlobal,0,1024 )

EndIf

If steamstate=0
Print "Steam is offline"
EndIf

Print "steamstate="+steamstate


While True

Local a$=ReadSteam()

Print "<steam>:"+a

If a="stats received!"

Local games_played2=GetSteamStat("games_played2")

games_played2=games_played2+1

SetSteamStat( "games_played2",games_played2 )

StoreSteamStats()

Print "recv: Game counter incremented to "+games_played2

Endif

If a="stats stored!"

Local games_played2=GetSteamStat("games_played2")

games_played2=games_played2+1

SetSteamStat( "games_played2",games_played2 )

StoreSteamStats()

Print "store: Game counter incremented to "+games_played2

EndIf

'sleep

Wend


End Function



Function Main()
Print "steam stub 1.1"
steamtest()
Print "finished, hit Return To finish"
' WaitKey
End

')

steamstub.cpp [beta monkey only release with bits disabled]


format_codebox('
#include <stdio.h>
#include <string>

#include "../../steam/steam_api.h"

#define BBDECL extern "C"
//BBDECL int OpenSteam(int gameID);

extern "C" long long __umoddi3( long long x, long long y) ;
extern "C" long long __udivdi3( long long x, long long y) ;

long long __umoddi3( long long x, long long y) {
printf("bleh");
fflush(stdout);
return x%y;
}

long long __udivdi3( long long x, long long y) {
printf("bleh");
fflush(stdout);
return x/y;
}

// _declspec(dllexport)

SteamAPICall_t call = 0;
SteamLeaderboard_t handle=0;
std::string steamlog;

class SteamStub
{
public:
CGameID m_GameID;

SteamStub( int32 gameID );

STEAM_CALLBACK( SteamStub, OnUserStatsReceived, UserStatsReceived_t, m_CallbackUserStatsReceived );
STEAM_CALLBACK( SteamStub, OnUserStatsStored, UserStatsStored_t, m_CallbackUserStatsStored );
STEAM_CALLBACK( SteamStub, OnAchievementStored, UserAchievementStored_t, m_CallbackAchievementStored );
STEAM_CALLBACK( SteamStub, OnFindScores, LeaderboardFindResult_t, m_CallbackFindScores );
STEAM_CALLBACK( SteamStub, OnDownload, LeaderboardScoresDownloaded_t, m_CallbackDownloaded );
STEAM_CALLBACK( SteamStub, OnUpload, LeaderboardScoreUploaded_t, m_CallbackUploaded );
STEAM_CALLBACK( SteamStub, OnCount, NumberOfCurrentPlayers_t, m_CallbackCount );

// Called when SteamUserStats()->FindOrCreateLeaderboard() returns asynchronously
// Called when SteamUserStats()->UploadLeaderboardScore() returns asynchronously

void OnFindLeaderboard( LeaderboardFindResult_t *pFindLearderboardResult, bool bIOFailure );
void OnUploadScore( LeaderboardScoreUploaded_t *pFindLearderboardResult, bool bIOFailure );

CCallResult<SteamStub, LeaderboardFindResult_t> m_SteamCallResultCreateLeaderboard;
CCallResult<SteamStub, LeaderboardScoreUploaded_t> m_SteamCallResultUploadScore;
};

SteamStub::SteamStub( int32 gameID ):
m_CallbackUserStatsReceived( this, &SteamStub::OnUserStatsReceived ),
m_CallbackUserStatsStored( this, &SteamStub::OnUserStatsStored ),
m_CallbackAchievementStored( this, &SteamStub::OnAchievementStored ),

m_CallbackFindScores( this, &SteamStub::OnFindScores ),
m_CallbackDownloaded( this, &SteamStub::OnDownload ),
m_CallbackUploaded( this, &SteamStub::OnUpload ),
m_CallbackCount( this, &SteamStub::OnCount ),
m_GameID( gameID )
{
// m_GameID.Set( uint32 unAccountID, EUniverse eUniverse, EAccountType eAccountType )
}


void SteamStub::OnDownload( LeaderboardScoresDownloaded_t *download ){

LeaderboardEntry_t entry;
int32 details[256];
char buffer[128];
int n;

_snprintf( buffer, 128, "download=%d!", download->m_cEntryCount );
steamlog.append(buffer);
for ( int index = 0; index < download->m_cEntryCount; index++ ){
SteamUserStats()->GetDownloadedLeaderboardEntry(
download->m_hSteamLeaderboardEntries,
index,
&entry,
details,
256
);
n=_snprintf( buffer, 128, "%d:%d:%d:", entry.m_nScore,entry.m_nGlobalRank,entry.m_steamIDUser);
steamlog.append(buffer);
for( int i=0;i<entry.m_cDetails;i++){
steamlog.push_back((char)details[i]);
}
steamlog.append("!");
}
}

void SteamStub::OnUpload( LeaderboardScoreUploaded_t *up ){
char buffer[128];
if(up->m_bSuccess && up->m_bScoreChanged){
_snprintf( buffer, 128, "upload=%d:%d:%d!", up->m_nScore,up->m_nGlobalRankNew,up->m_nGlobalRankPrevious );
steamlog.append(buffer);
}
}

void SteamStub::OnFindScores( LeaderboardFindResult_t *t ){
char buffer[128];
if(t->m_bLeaderboardFound){
handle=t->m_hSteamLeaderboard;
_snprintf( buffer, 128, "found=%s:%d!",
SteamUserStats()->GetLeaderboardName(t->m_hSteamLeaderboard),
SteamUserStats()->GetLeaderboardEntryCount(t->m_hSteamLeaderboard));
steamlog.append(buffer);
}
}


void SteamStub::OnFindLeaderboard( LeaderboardFindResult_t *pFindLearderboardResult, bool bIOFailure ){
printf("onfindleaderboard");fflush(stdout);
}

void SteamStub::OnUploadScore( LeaderboardScoreUploaded_t *pFindLearderboardResult, bool bIOFailure ){
printf("onfindleaderboard");fflush(stdout);
}


void SteamStub::OnCount(NumberOfCurrentPlayers_t *n){
char buffer[128];
_snprintf( buffer, 128, "playercount=%d!", n->m_cPlayers);
steamlog.append(buffer);
}

void SteamStub::OnUserStatsReceived( UserStatsReceived_t *pCallback )
{
// we may get callbacks for other games' stats arriving, ignore them
if ( m_GameID.ToUint64() == pCallback->m_nGameID )
{
if ( k_EResultOK == pCallback->m_eResult )
{
steamlog.append("stats received!");
}
else
{
char buffer[128];
_snprintf( buffer, 128, "stats failed %d!", pCallback->m_eResult );
steamlog.append( buffer );
}
}
}


void SteamStub::OnUserStatsStored( UserStatsStored_t *pCallback )
{
// we may get callbacks for other games' stats arriving, ignore them
if ( m_GameID.ToUint64() == pCallback->m_nGameID )
{
if ( k_EResultOK == pCallback->m_eResult )
{
steamlog.append("stats stored!");
}
else
{
char buffer[128];
_snprintf( buffer, 128, "stats stored failed %d!", pCallback->m_eResult );
steamlog.append( buffer );
}
}
}

void SteamStub::OnAchievementStored( UserAchievementStored_t *pCallback )
{
// we may get callbacks for other games' stats arriving, ignore them
if ( m_GameID.ToUint64() == pCallback->m_nGameID )
{
if ( 0 == pCallback->m_nMaxProgress )
{
char buffer[128];
_snprintf( buffer, 128, "Achievement '%s' unlocked!", pCallback->m_rgchAchievementName );
steamlog.append(buffer);
}
else
{
char buffer[128];
_snprintf( buffer, 128, "Achievement '%s' progress %d:%d!",
pCallback->m_rgchAchievementName, pCallback->m_nCurProgress, pCallback->m_nMaxProgress );
steamlog.append( buffer );
}
}
}





#define MAXPLAYERFETCH 256
#define BUFFERSIZE 4096+MAXPLAYERFETCH*512

char rbuffer[BUFFERSIZE];

BBDECL char *ReadSteam(){
SteamAPI_RunCallbacks();
_snprintf(rbuffer,BUFFERSIZE,"%s",steamlog.c_str());
steamlog="";
return rbuffer;
}

SteamStub *stub;

BBDECL void StoreSteamStats(){
SteamUserStats()->StoreStats();
}


BBDECL int GetSteamStat(String id){
int32 res;
char *c=id.ToCString<char>();
if (!SteamUserStats()->GetStat(c,&res )){
return -1;
}
return res;
}

BBDECL void SetSteamStat(String id,int32 value){
char *c=id.ToCString<char>();
SteamUserStats()->SetStat(c,value);
}

#ifdef features

// stats


BBDECL int GetSteamStat(const char *id){
int32 res;
if (!SteamUserStats()->GetStat(id,&res )){
return -1;
}
return res;
}

BBDECL void SetSteamStat(const char *id,int32 value){
SteamUserStats()->SetStat(id,value);
}

// achieves

BBDECL int GetSteamAchievement(char *id){
bool res;
SteamUserStats()->GetAchievement(id,&res );
return res?1:0;
}

BBDECL void SetSteamAchievement(const char *id){
SteamUserStats()->SetAchievement(id);
}

BBDECL void ClearSteamAchievement(const char *id){
SteamUserStats()->ClearAchievement(id);
}

BBDECL int GetSteamAchievementIcon(const char *pchName){
return SteamUserStats()->GetAchievementIcon( pchName );
}
// Get general attributes (display name / text, etc) for an Achievement
BBDECL const char *GetAchievementDisplayAttribute( const char *pchName, const char *pchKey ){
return SteamUserStats()->GetAchievementDisplayAttribute( pchName, pchKey );
}

// Achievement progress - triggers an AchievementProgress callback, that is all.
// Calling this w/ N out of N progress will NOT set the achievement, the game must still do that.

BBDECL int IndicateAchievementProgress( const char *pchName, uint32 nCurProgress, uint32 nMaxProgress ){
return SteamUserStats()->IndicateAchievementProgress( pchName, nCurProgress, nMaxProgress )?1:0;
}

char leadname[256];

BBDECL void FindOrCreateLeaderboard( char *name, ELeaderboardSortMethod sortmethod, ELeaderboardDisplayType displaytype ){
handle=0;
sprintf(leadname,name);
call=SteamUserStats()->FindOrCreateLeaderboard(leadname,sortmethod,displaytype);
if (call!=0){
stub->m_SteamCallResultCreateLeaderboard.Set( call, stub, &SteamStub::OnFindLeaderboard );
}
}

BBDECL void FindLeaderboard( char *name ){
handle=0;
sprintf(leadname,name);
call=SteamUserStats()->FindLeaderboard(leadname);
if (call!=0){
stub->m_SteamCallResultCreateLeaderboard.Set( call, stub, &SteamStub::OnFindLeaderboard );
}
}

BBDECL void UploadLeaderboardScore( ELeaderboardUploadScoreMethod method, int32 score, char *details ) {
static int data[256];
int i;
if(handle==0){
steamlog.append( "no leaderboard open!" );
return;
}

for(i=0;i<255;i++){
if ((data[i]=details[i])==0){
break;
}
}

data[i++]=0;
SteamUserStats()->UploadLeaderboardScore(handle,method,score,(const int32 *)data,i);
}

BBDECL void DownloadLeaderboardEntries( ELeaderboardDataRequest request, int start, int end ) {
if(handle==0){
steamlog.append( "no leaderboard open!" );
return;
}
if(end-start>MAXPLAYERFETCH){
end=start+MAXPLAYERFETCH;
}
SteamUserStats()->DownloadLeaderboardEntries(handle,request,start,end);
}

#endif

BBDECL void FindNumberOfCurrentPlayers() {
SteamUserStats()->GetNumberOfCurrentPlayers();
}

BBDECL void CloseSteam(){
SteamAPI_Shutdown();
}

BBDECL int OpenSteam(int gameID){

if(!SteamAPI_Init()){
return -1;
}

stub=new SteamStub( gameID );

CSteamID steamid;
int loggedon;

steamid=SteamUser()->GetSteamID();

loggedon=SteamUser()->BLoggedOn();

SteamUserStats()->RequestCurrentStats();
// SteamUserStats()->SetStat("Player hatched",50);
// SteamUserStats()->StoreStats();

return loggedon?1:0;
}

/*
BOOL APIENTRY DllMain( HMODULE hModule,
DWORD ul_reason_for_call,
LPVOID lpReserved
)
{
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
case DLL_THREAD_ATTACH:
case DLL_THREAD_DETACH:
case DLL_PROCESS_DETACH:
break;
}
return TRUE;
}
*/
')


Oddball(Posted 2013) [#9]
Thanks Skid. Had a quick look, and even though I don't understand most of it, it looks detailed enough for me to work it out. I'll do some testing and report back. Thanks.


skid(Posted 2015) [#10]
I just got this working today with latest steam sdk.

Once I have added a few more calls to the test project I will upload the steam target (based on modified GLFW3 target similar to above) so others do not have to go through the same pain.


MikeHart(Posted 2015) [#11]
THANK YOU!!!!!!!!


rIKmAN(Posted 2015) [#12]
Awesome, thanks skid MUCH appreciated!


skid(Posted 2015) [#13]
Small steps...






lom(Posted 2015) [#14]
skid,
That's awesome! I will definitely need this.
Also it would be nice to see a Steamworks multiplayer implementation in your module.


skid(Posted 2015) [#15]
Stats, achievements and leaderboard functions are in and MacOS is now supported.

Hopefully first public release will be ready sometime next week. I'm tempted to attach a smallish price tag to pay for some of the beers that have gone into this effort, not sure.


Why0Why(Posted 2015) [#16]
Skid, I think you should absolutely attach a small price to it. Anybody serious enough to get on Steam should appreciate your efforts.


skid(Posted 2015) [#17]
I'm thinking SteamWorks target for MacOS and Windows = 12 bottles so US$25 for 1 year support.

A Linux release would need a sponsor, US$400 to cover set up costs and pain would make it happen.


Soap(Posted 2015) [#18]
I can help sponsor.


Amon(Posted 2015) [#19]

I'm thinking SteamWorks target for MacOS and Windows = 12 bottles so US$25 for 1 year support.



Bargain of the week that is.


A Linux release would need a sponsor, US$400 to cover set up costs and pain would make it happen.



That's a bargain, also, considering you only want say $20 for the set up costs and $380 for the associated Linux pain. Brave soul if not a little self sadistic..... :)


skid(Posted 2015) [#20]
I wish Valve adopted Android, their still born Linux distro is typical of continued Linux=Fail in the game world.

Surely even in Eastern Europe XP desktop is preferable?


Why0Why(Posted 2015) [#21]
But, but, I hear this is the year of linux ;)


lom(Posted 2015) [#22]
skid,
Any plans to add multiplayer support?


skid(Posted 2015) [#23]
Ask me again in 12 months. If someone in the monkey community has such a project they know who to call.

Oh, and after seeing Linux in use today, I take back what I said about it being worthless. I will be investing in a 4K panel and SSD for linux version as soon as I make 10 sales @ $25. If that target takes 12 months I'm not bothered, no hurry.

There is a work in progress blog here.

http://nitrologic.blogspot.co.nz/2015/03/love-monkey.html

The error stuff is because I think stdout is null in callback land so printf is currently out of bounds during steamstub callback handlers.

Ideally I would like to use stdio over pipes instead of an event system as I consider native code creating monkey objects which it does at the moment pretty unsafe.


Raz(Posted 2015) [#24]
Skid, any more progress/change with this? I've just tried it with Monkey72b (nearest version to 72a that I could get) and am getting a couple of errors. I'm using platform toolset v100 and the steam SDK which I downloaded earlier today.

format_code(' ..\main.cpp(2885): error C2440: 'initializing' : cannot convert from 'String::CString<C>' to 'char *' [C:\tmp\steamtest\steamtest.build\steamglfw\vc2010\MonkeyGame.vcxproj]
..\main.cpp(2893): error C2440: 'initializing' : cannot convert from 'String::CString<C>' to 'char *' [C:\tmp\steamtest\steamtest.build\steamglfw\vc2010\MonkeyGame.vcxproj]')

I'm happy to offer sponsorship in some form or another if that's of any motivation? :D

Primarily looking at Windows support, but ideally want Windows, OSX and Linux