Steam Achievements

3rd party tools like asset generators, editors.
4 posts Page 1 of 1
Steve_Yorkshire
Posts: 252
Joined: Tue Feb 03, 2015 10:30 pm
 
by Steve_Yorkshire » Tue Jun 12, 2018 1:03 am
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");
Steve_Yorkshire
Posts: 252
Joined: Tue Feb 03, 2015 10:30 pm
 
by Steve_Yorkshire » Tue Jun 12, 2018 1:06 am
Oh and it's worth pointing out that at the time the game example Valve provided differed wildly from the available documentation, hence some of my code comments.
Jason Campbell
Posts: 269
Joined: Fri Feb 13, 2015 2:51 am
 
by Jason Campbell » Wed Jun 13, 2018 4:07 am
Image

Your posts are always filled with awesomeness.

Thank you Steve.
damik
Posts: 37
Joined: Thu Jun 23, 2016 12:02 pm
by damik » Wed Jun 13, 2018 11:16 am
thank you :D
4 posts Page 1 of 1

Who is online

Users browsing this forum: No registered users and 0 guests