Steve_Yorkshire Posted June 11, 2018 Share Posted June 11, 2018 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.cppsource/app/mainLoop.cppAdd 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"); Quote Link to comment Share on other sites More sharing options...
Steve_Yorkshire Posted June 11, 2018 Author Share Posted June 11, 2018 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. Quote Link to comment Share on other sites More sharing options...
Jason Campbell Posted June 13, 2018 Share Posted June 13, 2018 Your posts are always filled with awesomeness. Thank you Steve. Quote Link to comment Share on other sites More sharing options...
damik Posted June 13, 2018 Share Posted June 13, 2018 thank you :D Quote Link to comment Share on other sites More sharing options...
Steve_Yorkshire Posted Saturday at 03:14 PM Author Share Posted Saturday at 03:14 PM (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 Saturday at 03:17 PM by Steve_Yorkshire Quote Link to comment Share on other sites More sharing options...
Recommended Posts
Join the conversation
You can post now and register later. If you have an account, sign in now to post with your account.