rlabrecque / Steamworks.NET

Steamworks wrapper for Unity / C#
http://steamworks.github.io
MIT License
2.75k stars 364 forks source link

Steamworks.NET Callbacks Not Triggering for Achievements #571

Open tomtako opened 1 year ago

tomtako commented 1 year ago

Hello everyone,

I'm currently facing an issue with Steamworks.NET integration in my Unity project. The problem is that the callbacks for Steam APIs, specifically related to achievements, are not being triggered. Below, I'll outline the steps I've taken and the observations I've made:

Steps Taken:

  1. I used the Package Manager to add Steamworks.NET to my Unity project.
  2. I added the "SteamManager.cs" file to the project to handle Steam initialization.
  3. I created a new script called "AchievementController" to manage achievements.

Observations:

  1. The SteamManager seems to be running as expected, and its update method calls "SteamAPI.RunCallbacks();" properly.
  2. I have double-checked the App ID for my game using a CGameID object, and it is correct.
  3. My game has several achievements defined on the Steamworks developer portal, and they are marked as "PUBLISHED."
  4. I verified that the callbacks for achievements (e.g., "UserStatsReceived_t") are being created.
  5. The method "SteamUserStats.RequestCurrentStats()" returns TRUE, indicating that the stats are being requested from Steam.
  6. My Steam application is OPEN and shows "RUNNING" next to my game in the Steam client.

To troubleshoot the issue, I even modified the SteamManager script to trigger an event when initialized, thinking it might be a race condition. However, this approach didn't yield any success. I also tried disabling and destroying the SteamManager to eliminate potential conflicts, but the callbacks still aren't being called.

Below are the entirety of my relevant scripts:

AchievementController.cs

using System;
using Steamworks;
using UnityEngine;

namespace CrossBlitz.Steam
{
    public class AchievementController : MonoBehaviour
    {
        protected bool m_initialized;
        protected CGameID m_gameId;
        protected Callback<UserStatsReceived_t> m_userStatsReceived;

        protected bool m_requestedStats;
        protected bool m_statsAreValid;

        private void Awake()
        {
            SteamManager.OnSteamInitialized += OnSteamInitialized;
        }

        private void OnDestroy()
        {
            SteamManager.OnSteamInitialized -= OnSteamInitialized;
        }

        private void OnSteamInitialized()
        {
            if (!SteamManager.Initialized)
            {
                Debug.LogError("Steam manager not initialized.");
                return;
            }

            // Cache the GameID for use in the Callbacks
            m_gameId = new CGameID(SteamUtils.GetAppID());
            Debug.LogError($"GameId = {m_gameId.m_GameID}");
            m_userStatsReceived = Callback<UserStatsReceived_t>.Create(OnUserStatsReceived);
            m_initialized = true;

            string username = SteamFriends.GetPersonaName();
            Debug.LogError($"Name = {username}");
            //SteamFriends.ActivateGameOverlay( "Stats" );

            Debug.Log("Created the requested stats callback.");
        }

        private void Update()
        {
            if (!SteamManager.Initialized)
            {
                Debug.LogError("Steam manager not initialized.");
                return;
            }

            if (!m_initialized)
            {
                return;
            }

            //Debug.LogError($"m_userStatsReceived = {m_userStatsReceived != null}");

            if (!m_requestedStats && Input.GetKeyDown(KeyCode.A))
            {
                var success = SteamUserStats.RequestCurrentStats();
                //m_requestedStats = success;
                Debug.Log($"Requested Stats: {success}");
                // var achievementName = "ACH_MASTER_MELDER";
                // if (SteamUserStats.GetAchievement(achievementName, out bool achieved))
                // {
                //     Debug.Log($"Achievement {achievementName} success! Achieved? {achieved}");
                // }
                // else
                // {
                //     Debug.LogError($"Could not get {achievementName} did you publish changes?");
                // }
            }
        }

        private void OnUserStatsReceived(UserStatsReceived_t pCallback)
        {
            Debug.LogError($"Requested stats?? {pCallback.m_eResult}");

            if ((ulong)m_gameId != pCallback.m_nGameID)
            {
                Debug.LogError($"Received stats from {pCallback.m_nGameID}, ignoring.");
                return;
            }

            if (EResult.k_EResultOK != pCallback.m_eResult)
            {
                Debug.LogError($"Error receiving achievements {pCallback.m_eResult}, you should handle this.");
                return;
            }

            var achievementName = "ACH_MASTER_MELDER";

            if (SteamUserStats.GetAchievement(achievementName, out bool achieved))
            {
                Debug.Log($"Achievement {achievementName} success! Achieved? {achieved}");
            }
            else
            {
                Debug.LogError($"Could not get {achievementName} did you publish changes?");
            }
        }
    }
}

SteamManager.cs

// The SteamManager is designed to work with Steamworks.NET
// This file is released into the public domain.
// Where that dedication is not recognized you are granted a perpetual,
// irrevocable license to copy and modify this file as you see fit.
//
// Version: 1.0.12

#if !(UNITY_STANDALONE_WIN || UNITY_STANDALONE_LINUX || UNITY_STANDALONE_OSX || STEAMWORKS_WIN || STEAMWORKS_LIN_OSX)
#define DISABLESTEAMWORKS
#endif

using UnityEngine;
#if !DISABLESTEAMWORKS
using System;
using System.Collections;
using Steamworks;
#endif

//
// The SteamManager provides a base implementation of Steamworks.NET on which you can build upon.
// It handles the basics of starting up and shutting down the SteamAPI for use.
//
[DisallowMultipleComponent]
public class SteamManager : MonoBehaviour
{
#if !DISABLESTEAMWORKS
    public static event Action OnSteamInitialized;

    protected static bool s_EverInitialized = false;

    protected static SteamManager s_instance;

    protected static SteamManager Instance
    {
        get
        {
            if (s_instance == null)
            {
                s_instance =FindObjectOfType<SteamManager>();
                return s_instance;
                // new GameObject("SteamManager").AddComponent<SteamManager>();
            }
            else
            {
                return s_instance;
            }
        }
    }

    protected bool m_bInitialized = false;
    protected bool m_bSteamInitializedEventCalled = false;

    public static bool Initialized
    {
        get { return Instance.m_bInitialized; }
    }

    protected SteamAPIWarningMessageHook_t m_SteamAPIWarningMessageHook;

    [AOT.MonoPInvokeCallback(typeof(SteamAPIWarningMessageHook_t))]
    protected static void SteamAPIDebugTextHook(int nSeverity, System.Text.StringBuilder pchDebugText)
    {
        Debug.LogWarning(pchDebugText);
    }

#if UNITY_2019_3_OR_NEWER
    // In case of disabled Domain Reload, reset static members before entering Play Mode.
    [RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
    private static void InitOnPlayMode()
    {
        s_EverInitialized = false;
        s_instance = null;
    }
#endif

    protected virtual void Awake()
    {
        // Only one instance of SteamManager at a time!
        // if (s_instance != null)
        // {
        //     Destroy(gameObject);
        //     return;
        // }

        s_instance = this;

        if (s_EverInitialized)
        {
            // This is almost always an error.
            // The most common case where this happens is when SteamManager gets destroyed because of Application.Quit(),
            // and then some Steamworks code in some other OnDestroy gets called afterwards, creating a new SteamManager.
            // You should never call Steamworks functions in OnDestroy, always prefer OnDisable if possible.
            throw new System.Exception("Tried to Initialize the SteamAPI twice in one session!");
        }

        // We want our SteamManager Instance to persist across scenes.
        //DontDestroyOnLoad(gameObject);

        if (!Packsize.Test())
        {
            Debug.LogError(
                "[Steamworks.NET] Packsize Test returned false, the wrong version of Steamworks.NET is being run in this platform.",
                this);
        }

        if (!DllCheck.Test())
        {
            Debug.LogError(
                "[Steamworks.NET] DllCheck Test returned false, One or more of the Steamworks binaries seems to be the wrong version.",
                this);
        }

        try
        {
            // If Steam is not running or the game wasn't started through Steam, SteamAPI_RestartAppIfNecessary starts the
            // Steam client and also launches this game again if the User owns it. This can act as a rudimentary form of DRM.

            // Once you get a Steam AppID assigned by Valve, you need to replace AppId_t.Invalid with it and
            // remove steam_appid.txt from the game depot. eg: "(AppId_t)480" or "new AppId_t(480)".
            // See the Valve documentation for more information: https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
            if (SteamAPI.RestartAppIfNecessary(AppId_t.Invalid))
            {
                Application.Quit();
                return;
            }
        }
        catch (System.DllNotFoundException e)
        {
            // We catch this exception here, as it will be the first occurrence of it.
            Debug.LogError(
                "[Steamworks.NET] Could not load [lib]steam_api.dll/so/dylib. It's likely not in the correct location. Refer to the README for more details.\n" +
                e, this);

            Application.Quit();
            return;
        }

        // Initializes the Steamworks API.
        // If this returns false then this indicates one of the following conditions:
        // [*] The Steam client isn't running. A running Steam client is required to provide implementations of the various Steamworks interfaces.
        // [*] The Steam client couldn't determine the App ID of game. If you're running your application from the executable or debugger directly then you must have a [code-inline]steam_appid.txt[/code-inline] in your game directory next to the executable, with your app ID in it and nothing else. Steam will look for this file in the current working directory. If you are running your executable from a different directory you may need to relocate the [code-inline]steam_appid.txt[/code-inline] file.
        // [*] Your application is not running under the same OS user context as the Steam client, such as a different user or administration access level.
        // [*] Ensure that you own a license for the App ID on the currently active Steam account. Your game must show up in your Steam library.
        // [*] Your App ID is not completely set up, i.e. in Release State: Unavailable, or it's missing default packages.
        // Valve's documentation for this is located here:
        // https://partner.steamgames.com/doc/sdk/api#initialization_and_shutdown
        m_bInitialized = SteamAPI.Init();

        if (!m_bInitialized)
        {
            Debug.LogError(
                "[Steamworks.NET] SteamAPI_Init() failed. Refer to Valve's documentation or the comment above this line for more information.",
                this);

            return;
        }

        s_EverInitialized = true;
    }

    // This should only ever get called on first load and after an Assembly reload, You should never Disable the Steamworks Manager yourself.
    protected virtual void OnEnable()
    {
        if (s_instance == null)
        {
            s_instance = this;
        }

        if (!m_bInitialized)
        {
            return;
        }

        if (m_SteamAPIWarningMessageHook == null)
        {
            // Set up our callback to receive warning messages from Steam.
            // You must launch with "-debug_steamapi" in the launch args to receive warnings.
            m_SteamAPIWarningMessageHook = new SteamAPIWarningMessageHook_t(SteamAPIDebugTextHook);
            SteamClient.SetWarningMessageHook(m_SteamAPIWarningMessageHook);
        }
    }

    // OnApplicationQuit gets called too early to shutdown the SteamAPI.
    // Because the SteamManager should be persistent and never disabled or destroyed we can shutdown the SteamAPI here.
    // Thus it is not recommended to perform any Steamworks work in other OnDestroy functions as the order of execution can not be garenteed upon Shutdown. Prefer OnDisable().
    protected virtual void OnDestroy()
    {
        if (s_instance != this)
        {
            return;
        }

        s_instance = null;

        if (!m_bInitialized)
        {
            return;
        }

        SteamAPI.Shutdown();
    }

    protected virtual void Update()
    {
        if (!m_bInitialized)
        {
            return;
        }

        if (!m_bSteamInitializedEventCalled)
        {
            m_bSteamInitializedEventCalled = true;
            OnSteamInitialized?.Invoke();
        }

        // Run Steam client callbacks
        SteamAPI.RunCallbacks();
    }
#else
    public static bool Initialized {
        get {
            return false;
        }
    }
#endif // !DISABLESTEAMWORKS
}
tomtako commented 1 year ago

Bump on this. Hope someone can help

bplane2 commented 8 months ago

@tomtako Did you ever find a solution for this? We're having the same issue currently.

tomtako commented 8 months ago

I did, I didn’t do anything, it seemed to start working after a day or 2 of being published. I’m not sure why it took so long to register.