Jump to content

Steam Achievements


Steve_Yorkshire

Recommended Posts

Quick thread on how I set up steam achievements for Airship Dragoon - NOTE: this was done years back with Torque3D 3.8 and I had no idea what I was doing - but I've never let that stop me! :lol:


First up add all of the neccessary steamworks files to your project solution. As I wasn't using stats or anything complicated I only needed the basics for steam API and achievements.


What I did was register all the achievements in C++ and then call them via events in Torquescript.


Registering your achievements. I created source/steam/steamachievements.cpp/h (which was just my version of Steam's example achievements and Stats file) and listed my achievements numerically using the tag ACH_AD_# ( Achievement Airship Dragoon number) with the appropriate name that would display in Steam.


Now there is a lot of commenting stuff out - mostly because I had no idea what I was doing - so I added everything and then rolled back what I didn't need.

 

Achievement_t g_rgAchievements[] =
{
	_ACH_ID( ACH_AD_1, "MIRACLE" ),
	_ACH_ID( ACH_AD_2, "HEROIC" ),
	_ACH_ID( ACH_AD_3, "PROFESSIONAL" ),
	_ACH_ID( ACH_AD_4, "AGAINST THE ODDS" ),
	_ACH_ID( ACH_AD_5, "TURNING THE TIDE" ),
	_ACH_ID( ACH_AD_6, "STIFF ODDS" ),
	_ACH_ID( ACH_AD_7, "HARD VICTORY" ),	
	_ACH_ID( ACH_AD_8, "DECISIVE VICTORY" ),
	_ACH_ID( ACH_AD_9, "MINOR VICTORY" ),
	_ACH_ID( ACH_AD_10, "DIFFICULT" ),
	_ACH_ID( ACH_AD_11, "LUCKY" ),
	_ACH_ID( ACH_AD_12, "JAWS OF DEFEAT" ),
	_ACH_ID( ACH_AD_13, "PYRRHIC" ),
	_ACH_ID( ACH_AD_14, "NICE DAY OUT" ),	
	_ACH_ID( ACH_AD_15, "HAT TRICK" ),
	_ACH_ID( ACH_AD_16, "LIVE TO FIGHT ANOTHER DAY" ),
	_ACH_ID( ACH_AD_17, "THE GUVNER" ),
	_ACH_ID( ACH_AD_18, "PUMMEL A PIRATE" ),
	_ACH_ID( ACH_AD_19, "IRON CURTAIN" ),
	_ACH_ID( ACH_AD_20, "BULLDOZER" ),
	_ACH_ID( ACH_AD_21, "FAIRY TALE" ),
	_ACH_ID( ACH_AD_22, "AIRSHIP JACKER" ),
	_ACH_ID( ACH_AD_23, "ONCE MORE UNTO THE BREACH" ),
	_ACH_ID( ACH_AD_24, "HIGH TECH" ),
	_ACH_ID( ACH_AD_25, "ALL YOUR COAL BELONG TO US" ),
	_ACH_ID( ACH_AD_26, "FULL HOUSE" ),
	_ACH_ID( ACH_AD_27, "PIRATE PULVERIZER" ),
};

 

And the whole file looked like this:

//yorks steam achievements
 
//Define T3D Console
#include "platform/platform.h"
#include "console/console.h"
#include "console/consoleInternal.h"
#include "console/engineAPI.h"
 
/*
#include "steam_api.h"//yorks steam
#include "isteamapplist.h"
#include "isteamapps.h"
#include "isteamappticket.h"
#include "isteamclient.h"
#include "isteamuser.h"
#include "isteamuserstats.h"
#include "isteamutils.h"*/
#include "steam/SteamAchievements.h"
 
#define ACHDISP_FONT_HEIGHT 20
#define ACHDISP_COLUMN_WIDTH 340
#define ACHDISP_CENTER_SPACING 40
#define ACHDISP_VERT_SPACING 10
#define ACHDISP_IMG_SIZE 64
#define ACHDISP_IMG_PAD 10
 
#define _ACH_ID( id, name ) { id, #id, name, "", 0, 0 }
 
Achievement_t g_rgAchievements[] =
{
	_ACH_ID( ACH_AD_1, "MIRACLE" ),
	_ACH_ID( ACH_AD_2, "HEROIC" ),
	_ACH_ID( ACH_AD_3, "PROFESSIONAL" ),
	_ACH_ID( ACH_AD_4, "AGAINST THE ODDS" ),
	_ACH_ID( ACH_AD_5, "TURNING THE TIDE" ),
	_ACH_ID( ACH_AD_6, "STIFF ODDS" ),
	_ACH_ID( ACH_AD_7, "HARD VICTORY" ),	
	_ACH_ID( ACH_AD_8, "DECISIVE VICTORY" ),
	_ACH_ID( ACH_AD_9, "MINOR VICTORY" ),
	_ACH_ID( ACH_AD_10, "DIFFICULT" ),
	_ACH_ID( ACH_AD_11, "LUCKY" ),
	_ACH_ID( ACH_AD_12, "JAWS OF DEFEAT" ),
	_ACH_ID( ACH_AD_13, "PYRRHIC" ),
	_ACH_ID( ACH_AD_14, "NICE DAY OUT" ),	
	_ACH_ID( ACH_AD_15, "HAT TRICK" ),
	_ACH_ID( ACH_AD_16, "LIVE TO FIGHT ANOTHER DAY" ),
	_ACH_ID( ACH_AD_17, "THE GUVNER" ),
	_ACH_ID( ACH_AD_18, "PUMMEL A PIRATE" ),
	_ACH_ID( ACH_AD_19, "IRON CURTAIN" ),
	_ACH_ID( ACH_AD_20, "BULLDOZER" ),
	_ACH_ID( ACH_AD_21, "FAIRY TALE" ),
	_ACH_ID( ACH_AD_22, "AIRSHIP JACKER" ),
	_ACH_ID( ACH_AD_23, "ONCE MORE UNTO THE BREACH" ),
	_ACH_ID( ACH_AD_24, "HIGH TECH" ),
	_ACH_ID( ACH_AD_25, "ALL YOUR COAL BELONG TO US" ),
	_ACH_ID( ACH_AD_26, "FULL HOUSE" ),
	_ACH_ID( ACH_AD_27, "PIRATE PULVERIZER" ),
};
 
//CSteamAchievements*	g_SteamAchievements = NULL;
//CSteamAchievements*	g_SteamAchievements;
//-----------------------------------------------------------------------------
// Purpose: Constructor
//-----------------------------------------------------------------------------
#pragma warning( push )
//  warning C4355: 'this' : used in base member initializer list
//  This is OK because it's warning on setting up the Steam callbacks, they won't use this until after construction is done
#pragma warning( disable : 4355 ) 
/**
* Constructor
*/
CSteamAchievements::CSteamAchievements(Achievement_t *Achievements, int NumAchievements) :
//m_pSteamUser(NULL),
//m_pSteamUserStats(NULL),
m_iAppID(0),
m_bInitialized(false),
m_CallbackUserStatsReceived(this, &CSteamAchievements::OnUserStatsReceived),
m_CallbackUserStatsStored(this, &CSteamAchievements::OnUserStatsStored),
m_CallbackAchievementStored(this, &CSteamAchievements::OnAchievementStored)
{
	m_iAppID = SteamUtils()->GetAppID();
	m_pAchievements = Achievements;
	m_iNumAchievements = NumAchievements;
 
	#ifndef TORQUE_PLAYER//not player build, must be tools
	Con::errorf("steamAchievements constructor AppID %d", m_iAppID); 
	#endif
 
	RequestStats();
}
 
/*
* Destructor
*/
CSteamAchievements::~CSteamAchievements()
{
}
#pragma warning( pop )
 
bool CSteamAchievements::RequestStats()
{
	// Is Steam loaded? If not we can't get stats.
	if (NULL == SteamUserStats() || NULL == SteamUser())
	{
		Con::errorf("request stats fail - null steamUserStats or steamUser");
		return false;
	}
	// Is the user logged on?  If not we can't get stats.
	if (!SteamUser()->BLoggedOn())
	{
		Con::errorf("request stats fail - steamUser !loggedOn");
		return false;
	}
 
	// Request user stats.
	#ifndef TORQUE_PLAYER//not player build, must be tools
	Con::errorf("request stats success");
	#endif
 
	return SteamUserStats()->RequestCurrentStats();
}
 
bool CSteamAchievements::SetAchievement(const char* ID)
{
	// Have we received a call back from Steam yet?
	if (m_bInitialized)
	{
		SteamUserStats()->SetAchievement(ID);
		return SteamUserStats()->StoreStats();
	}
	// If not then we can't set achievements yet
	return false;
}
 
void CSteamAchievements::OnUserStatsReceived(UserStatsReceived_t *pCallback)
{
	// we may get callbacks for other games' stats arriving, ignore them
	if (m_iAppID == pCallback->m_nGameID)
	{
		if (k_EResultOK == pCallback->m_eResult)
		{
			//OutputDebugString("Received stats and achievements from Steam\n");
			//printf("Received stats and achievements from Steam\n");
			//Con::errorf("Received stats and achievements from Steam");
			m_bInitialized = true;
 
			#ifndef TORQUE_PLAYER//not player build, must be tools
			Con::errorf("achievement total %d", m_iNumAchievements);
			#endif
 
			// load achievements
			for (int iAch = 0; iAch < m_iNumAchievements; ++iAch)
			{
				Achievement_t &ach = m_pAchievements[iAch];
 
				#ifndef TORQUE_PLAYER//not player build, must be tools
				Con::printf("achievement num %d", iAch);
				Con::printf("achievement %d", &ach);
				#endif
 
				SteamUserStats()->GetAchievement(ach.m_pchAchievementID, &ach.m_bAchieved);
 
				#ifndef TORQUE_PLAYER//not player build, must be tools
				Con::printf("achievement id %d", ach.m_pchAchievementID);
				Con::printf(ach.m_rgchName, "%s",
					SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID, "name"));
 
				_snprintf(ach.m_rgchName, sizeof(ach.m_rgchName), "%s",
					SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID,
					"name"));
				_snprintf(ach.m_rgchDescription, sizeof(ach.m_rgchDescription), "%s",
					SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID,
					"desc"));
				#endif
 
			}
 
		}
		else
		{
			char buffer[128];
			_snprintf(buffer, 128, "RequestStats - failed, %d\n", pCallback->m_eResult);
			//OutputDebugString(buffer);
			Con::errorf("Receive stats and achievements failed %d", buffer);
		}
	}
}
 
void CSteamAchievements::OnUserStatsStored(UserStatsStored_t *pCallback)
{
	// we may get callbacks for other games' stats arriving, ignore them
	if (m_iAppID == pCallback->m_nGameID)
	{
		if (k_EResultOK == pCallback->m_eResult)
		{
			//OutputDebugString("Stored stats for Steam\n");
			Con::printf("Stored stats for Steam\n");
		}
		else
		{
			char buffer[128];
			_snprintf(buffer, 128, "StatsStored - failed, %d\n", pCallback->m_eResult);
			//OutputDebugString(buffer);
			//printf(buffer);
			Con::errorf("Store stats for Steam failed %d", buffer);
		}
	}
}
 
void CSteamAchievements::OnAchievementStored(UserAchievementStored_t *pCallback)
{
	// we may get callbacks for other games' stats arriving, ignore them
	if (m_iAppID == pCallback->m_nGameID)
	{
		//OutputDebugString("Stored Achievement for Steam\n");
		Con::printf("Stored Achievement for Steam\n");
	}
	else
	{
		Con::errorf("FAILED to Store Achievement for Steam\n");
	}
}
 
/*
ConsoleFunction(steamAchieve, bool, 2, 2, "SetAchievement(ID) - Set Steam achievement")//setAchievement
{
	TORQUE_UNUSED(argc);
	const char *ID = argv[1];
#ifndef TORQUE_PLAYER//not player build, must be tools
	Con::errorf("setachievements ID %d", ID);
#endif
	if (g_SteamAchievements)
	{
		return g_SteamAchievements->SetAchievement(ID);
	}
	else
	{
		Con::errorf("setachievements failed - no g_SteamAchievements");
	}
}
*/

 

And the header

#ifndef _STEAMACHIEVEMENTS_H
#define _STEAMACHIEVEMENTS_H
 
#include "steam/steam_api.h"
//#include "isteamapplist.h"
//#include "isteamapps.h"
//#include "isteamappticket.h"
//#include "isteamclient.h"
//#include "isteamuser.h"
//#include "isteamuserstats.h"
//#include "isteamutils.h"
 
/*
//Define T3D Console
#include "platform/platform.h"
#include "console/console.h"
#include "console/consoleInternal.h"
#include "console/engineAPI.h"*/
 
enum EAchievements
{
	ACH_AD_1 = 0,
	ACH_AD_2 = 1,
	ACH_AD_3 = 2,
	ACH_AD_4 = 3,
	ACH_AD_5 = 4,
	ACH_AD_6 = 5,
	ACH_AD_7 = 6,
	ACH_AD_8 = 7,
	ACH_AD_9 = 8,
	ACH_AD_10 = 9,
	ACH_AD_11 = 10,
	ACH_AD_12 = 11,
	ACH_AD_13 = 12,
	ACH_AD_14 = 13,	
	ACH_AD_15 = 14,
	ACH_AD_16 = 15,
	ACH_AD_17 = 16,
	ACH_AD_18 = 17,
	ACH_AD_19 = 18,
	ACH_AD_20 = 19,
	ACH_AD_21 = 20,
	ACH_AD_22 = 21,
	ACH_AD_23 = 22,
	ACH_AD_24 = 23,
	ACH_AD_25 = 24,
	ACH_AD_26 = 25,
	ACH_AD_27 = 26,
};
 
struct Achievement_t
{
	EAchievements m_eAchievementID;
	const char *m_pchAchievementID;
	char m_rgchName[128];
	char m_rgchDescription[256];
	bool m_bAchieved;
	int m_iIconImage;
};
 
//class ISteamUser;//yorks ?
//class ISteamUserStats;//yorks
 
class CSteamAchievements
{
private:
	int64 m_iAppID; // Our current AppID
	Achievement_t *m_pAchievements; // Achievements data
	int m_iNumAchievements; // The number of Achievements
	bool m_bInitialized; // Have we called Request stats and received the callback?
 
	// Steam User interface
	//ISteamUser *m_pSteamUser;//yorks ? from example not docs
 
	// Steam UserStats interface
	//ISteamUserStats *m_pSteamUserStats;//yorks ? from example not docs
 
public:
	CSteamAchievements(Achievement_t *Achievements, int NumAchievements);
	~CSteamAchievements();
 
	bool RequestStats();
	bool SetAchievement(const char* ID);
 
	//void steamAchieve(const char*);
	STEAM_CALLBACK(CSteamAchievements, OnUserStatsReceived, UserStatsReceived_t,
		m_CallbackUserStatsReceived);
	STEAM_CALLBACK(CSteamAchievements, OnUserStatsStored, UserStatsStored_t,
		m_CallbackUserStatsStored);
	STEAM_CALLBACK(CSteamAchievements, OnAchievementStored,
		UserAchievementStored_t, m_CallbackAchievementStored);
};
 
#endif // _STEAMACHIEVEMENTS_H

 

I also have the code setup not to fire achivement calls if we're running in tools mode.


Now for some reason ... that escapes me ... I commented out the console function here and moved it to mainLoop.cpp


source/app/mainLoop.cpp


Add the includes for steamworks and our achievement file and init it all

 

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

 

I made my connection to steam overlay and then the achievements

// Process a time event and update all sub-processes
void processTimeEvent(S32 elapsedTime)
{
//...
 
   // Update the console time
   Con::setFloatVariable("Sim::Time",F32(Platform::getVirtualMilliseconds()) / 1000);
 
   SteamAPI_RunCallbacks();//yorks
   //Con::errorf("steamApi_runcallbacks");//yes this is the place!
   /*
   bool bRet = SteamUtils()->IsOverlayEnabled();
 
   if (!bRet)
   {
	   Con::errorf("Overlay not available");
   }
   */
}
 
CSteamAchievements*	g_SteamAchievements = NULL;//yorks
Achievement_t g_rgAchievements[];//yorks
 
void StandardMainLoop::init()
{
//...
   #ifdef TORQUE_DEBUG_GUARD
      Memory::flagCurrentAllocs( Memory::FLAG_Static );
   #endif
 
	  // Initialize Steam
	  bool bRet = SteamAPI_Init();
	  // Create the SteamAchievements object if Steam was successfully initialized
	  if (bRet)
	  {
		  // Tell Steam where it's overlay should show notification dialogs, this can be top right, top left,
		  // bottom right, bottom left. The default position is the bottom left if you don't call this.  
		  // Generally you should use the default and not call this as users will be most comfortable with 
		  // the default position.  The API is provided in case the bottom right creates a serious conflict 
		  // with important UI in your game.
		  //SteamUtils()->SetOverlayNotificationPosition(k_EPositionTopLeft);
 
		#ifndef TORQUE_PLAYER//not player build, must be tools
		  Con::errorf("steamApi Init");
		#endif
 
		  g_SteamAchievements = new CSteamAchievements(g_rgAchievements, 27);//27
 
			#ifndef TORQUE_PLAYER//not player build, must be tools
			Con::errorf("steamApi Init g_steamachievements %d", g_SteamAchievements);
			#endif
 
			SteamUtils()->SetOverlayNotificationPosition(k_EPositionTopLeft);
 
			//SteamFriends()->ActivateGameOverlay("Achievements");//"Friends", "Community", "Settings"
			//SteamFriends()->ActivateGameOverlayToWebPage("http://store.steampowered.com/app/308380/");
	  }
	  else
	  {
		  Con::errorf("steamApi Init failed");
	  }
}

 

Next I had my console function moved here for ... I have no idea ... reasons ...

Again only allowing achievements to trigger in the player build

 
ConsoleFunction(steamAchieve, bool, 2, 2, "SetAchievement(ID) - Set Steam achievement")//setAchievement
{
	TORQUE_UNUSED(argc);
	const char *ID = argv[1];
	#ifndef TORQUE_PLAYER//not player build, must be tools
	Con::errorf("setachievements ID %d", ID);
	#endif
	if (g_SteamAchievements)
		return g_SteamAchievements->SetAchievement(ID);
	else
		Con::errorf("setachievements failed - no g_SteamAchievements");
	return 0;//yorks added return 0 just to reduce console spam
}

 

And finally shut it down and delete the achievement object or it will keep running in the background

 

void StandardMainLoop::shutdown()
{
	// Shutdown Steam
	SteamAPI_Shutdown();//yorks
#ifndef TORQUE_PLAYER//not player build, must be tools
	Con::errorf("shutdown!");
#endif
 
	// Delete the SteamAchievements object
	if (g_SteamAchievements)//yorks
	{
		delete g_SteamAchievements;
		#ifndef TORQUE_PLAYER//not player build, must be tools
		Con::errorf("delete achievement object");
		#endif
	}
 
   // Stop the Input Event Manager
   INPUTMGR->stop();
//...
}

 

And remember to add the includes to the header file

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

 

Now in torquescript I would call an achievement using this script function and the achievement ID number.

      steamAchieve("ACH_AD_16");
Link to comment
Share on other sites

  • 6 years later...

Update December 2024 - so 6 years later ...
Valve have changed how Steamworks accesses player/client stats and data. Stats and achievements are now pre-loaded to the Steam Client BEFORE the game app is initialzied. This means CSteamAchievements::OnUserStatsReceived(UserStatsReceived_t *pCallback) will never be reached and calls to test whether client stats and achievements have been loaded will return false ... but are actually true. 🙄

These changes should get things back on track:

steamAchievements.cpp
 

bool CSteamAchievements::RequestStats()
{
	// Is Steam loaded? If not we can't get stats.
	if (NULL == SteamUserStats() || NULL == SteamUser())
	{
		Con::errorf("request stats fail - null steamUserStats or steamUser");
		return false;
	}
	// Is the user logged on?  If not we can't get stats.
	if (!SteamUser()->BLoggedOn())
	{
		Con::errorf("request stats fail - steamUser !loggedOn");
		return false;
	}

	// Request user stats.
	#ifdef TORQUE_PLAYER//not player build, must be tools
	Con::errorf("request stats success");
	#endif

	//yorks start new
	// load achievements
	for (int iAch = 0; iAch < m_iNumAchievements; ++iAch)
	{
		Achievement_t &ach = m_pAchievements[iAch];

		#ifndef TORQUE_PLAYER//if not player build, must be tools
				Con::printf("achievement num %d", iAch);
				Con::printf("achievement %d", &ach);
		#endif

				SteamUserStats()->GetAchievement(ach.m_pchAchievementID, &ach.m_bAchieved);

		#ifndef TORQUE_PLAYER//if not player build, must be tools
				Con::printf("achievement id %d", ach.m_pchAchievementID);
				Con::printf(ach.m_rgchName, "%s", SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID, "name"));

				_snprintf(ach.m_rgchName, sizeof(ach.m_rgchName), "%s",
					SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID,
					"name"));
				_snprintf(ach.m_rgchDescription, sizeof(ach.m_rgchDescription), "%s",
					SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID,
					"desc"));
		#else
			Con::printf("%s achieved? %d", SteamUserStats()->GetAchievementDisplayAttribute(ach.m_pchAchievementID, "name"), ach.m_bAchieved);
		#endif

	}//yorks end new

	return true;//yorks as of steamworks sdk 1.61 stats are loaded via client before game launch// SteamUserStats()->RequestCurrentStats();
}

And hopefully that helps fix things if you are still using Torque3D 3.x with Steam.

Edited by Steve_Yorkshire
Link to comment
Share on other sites

Join the conversation

You can post now and register later. If you have an account, sign in now to post with your account.

Guest
Reply to this topic...

×   Pasted as rich text.   Paste as plain text instead

  Only 75 emoji are allowed.

×   Your link has been automatically embedded.   Display as a link instead

×   Your previous content has been restored.   Clear editor

×   You cannot paste images directly. Upload or insert images from URL.

×
×
  • Create New...