Jump to content

Recommended Posts

Posted

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");
  • 6 years later...
Posted (edited)

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

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...