.. contents:: Table of Contents .. _steam: Module steam ============ The :guilabel:`steam` plugin integrates `Steamworks SDK `__ into **mini-mbm** games, exposing achievements, stats, leaderboards, cloud saves, overlay dialogs, and DLC detection to **Lua**. .. Important:: Steam is a **desktop** platform. This plugin is supported on **Windows**, **Linux**, and **macOS** only. It is not available on Android or iOS. .. Note:: Load the plugin with ``require "steam"`` (or ``require "libsteam"``) inside ``onInitScene()``. Always check :ref:`steam.isReady() ` before calling any other function — it returns ``false`` if the Steam client is not running or the user does not own the game. Prerequisites ------------- 1. **Steamworks Partner Account** — `enrol here `__. Each title uses its own App ID; the plugin never hard-codes it. 2. **Steamworks SDK** — download from the partner portal under *Developer Tools → Steamworks SDK*. Extract it locally (e.g. ``/home/user/steamworks_sdk`` or ``C:\steamworks_sdk``). 3. **Steam client** — must be running on the development machine during testing. 4. **steam_appid.txt** — create a plain-text file containing only your App ID (e.g. ``480``) and place it next to the ``mini-mbm`` executable for development runs. Retail builds launched via the Steam client receive the App ID automatically. Building -------- Linux / macOS (CMake) ^^^^^^^^^^^^^^^^^^^^^ .. code-block:: sh mkdir -p build/linux_debug && cd build/linux_debug cmake ../.. \ -DPLAT=Linux \ -DUSE_ALL=1 \ -DUSE_STEAM=1 \ -DSTEAMWORKS_SDK_PATH=/path/to/steamworks_sdk \ -DCMAKE_BUILD_TYPE=Debug make -j$(nproc) Replace ``Linux`` with ``MacOs`` for macOS, and ``Debug`` with ``Release`` for distribution builds. Windows (Visual Studio 2022) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ 1. Open ``platform-msvs/mini-mbm.sln`` in Visual Studio 2022. 2. Set the ``STEAMWORKS_SDK_PATH`` environment variable **before** opening Visual Studio: - Open the Start menu and search for **"Edit the system environment variables"**. - Click *Environment Variables…*, then under *User variables* click **New**. - Name: ``STEAMWORKS_SDK_PATH`` Value: ``C:\steamworks_sdk`` *(your actual path)*. - Click OK on all dialogs, then **restart Visual Studio**. 3. The **steam** project is disabled by default in Configuration Manager (no SDK headers available without the SDK). Enable it: go to **Build → Configuration Manager** and check the **Build** checkbox for the **steam** project. 4. Build the **steam** project. ``copy-steam-dll.bat`` runs automatically after a successful build and copies the correct Steam DLL (``steam_api64.dll`` or ``steam_api.dll``) into the output folder. 5. Place ``steam_appid.txt`` (containing only your App ID number, e.g. ``480``) next to ``mini-mbm.exe``. Windows (CMake + MinGW) ^^^^^^^^^^^^^^^^^^^^^^^^ .. code-block:: bat mkdir build\mingw_debug && cd build\mingw_debug cmake ..\.. -G "MinGW Makefiles" ^ -DPLAT=Windows ^ -DUSE_ALL=1 ^ -DUSE_STEAM=1 ^ -DSTEAMWORKS_SDK_PATH=C:\steamworks_sdk ^ -DCMAKE_BUILD_TYPE=Debug mingw32-make -j%NUMBER_OF_PROCESSORS% Runtime Deployment ------------------ The Steam shared library is copied to the output directory automatically as a post-build step. For **final distribution**, include the Steam library in your shipped game folder: +------------------+----------------------------------------------------------+ | Platform | Library to ship | +==================+==========================================================+ | Windows x64 | ``redistributable_bin\win64\steam_api64.dll`` | +------------------+----------------------------------------------------------+ | Windows x86 | ``redistributable_bin\steam_api.dll`` | +------------------+----------------------------------------------------------+ | Linux x64 | ``redistributable_bin/linux64/libsteam_api.so`` | +------------------+----------------------------------------------------------+ | macOS | ``redistributable_bin/osx/libsteam_api.dylib`` | +------------------+----------------------------------------------------------+ Lua API ------- Lifecycle & Info ^^^^^^^^^^^^^^^^ .. _steam_isReady: .. data:: steam.isReady() Returns ``true`` if the Steam client is running and the plugin initialised successfully. Call this first — all other functions return ``nil`` (or ``false``) when Steam is not ready. :return: ``boolean`` *Example:* .. code-block:: lua local steam = require "steam" function onInitScene() if not steam.isReady() then print("Steam not available") return end print("Steam OK, player:", steam.getPlayerName()) end .. data:: steam.getPlayerName() Returns the logged-in Steam user's display name. :return: ``string`` .. data:: steam.getAppId() Returns the game's unique Steam App ID. :return: ``integer`` .. data:: steam.getCurrentLanguage() Returns the user's game language preference (e.g. ``"english"``). :return: ``string`` Achievements ^^^^^^^^^^^^ .. data:: steam.setAchievement(id) Unlocks an achievement and immediately syncs it to Steam (no manual ``storeStats()`` call needed). :param string: **id** — achievement API name as defined in the Steamworks partner dashboard (e.g. ``"ACH_WIN_FIRST_GAME"``). :return: ``boolean`` — ``true`` if the unlock and store succeeded. *Example:* .. code-block:: lua steam.setAchievement("ACH_WIN_FIRST_GAME") .. data:: steam.clearAchievement(id) Clears (locks) an achievement. Intended for testing only. :param string: **id** — achievement API name. :return: ``boolean`` — ``true`` if the clear and store succeeded. .. data:: steam.isAchievementAchieved(id) Checks whether an achievement is already unlocked. :param string: **id** — achievement API name. :return: ``boolean`` — ``true`` if unlocked. Stats ^^^^^ .. Note:: ``setStatInt`` and ``setStatFloat`` update the stat locally. Call :data:`steam.storeStats()` afterwards to flush all pending changes to the Steam servers. .. data:: steam.setStatInt(name, value) Sets an integer stat locally. :param string: **name** — stat name as defined in the Steamworks partner dashboard. :param number: **value** — integer value to set. :return: ``boolean`` — ``true`` if the set call succeeded. .. data:: steam.setStatFloat(name, value) Sets a float stat locally. :param string: **name** — stat name. :param number: **value** — float value to set. :return: ``boolean`` — ``true`` if the set call succeeded. .. data:: steam.getStatInt(name) Retrieves a cached integer stat value. :param string: **name** — stat name. :return: ``integer`` — cached value (``0`` if not yet set). .. data:: steam.getStatFloat(name) Retrieves a cached float stat value. :param string: **name** — stat name. :return: ``number`` — cached value (``0.0`` if not yet set). .. data:: steam.storeStats() Flushes all pending stat changes to the Steam servers. Call this after a batch of ``setStatInt`` / ``setStatFloat`` calls. :return: ``boolean`` — ``true`` if the store call succeeded. *Example:* .. code-block:: lua steam.setStatInt("NumWins", steam.getStatInt("NumWins") + 1) steam.setStatFloat("FarthestDistance", 1234.5) steam.storeStats() Leaderboards (async) ^^^^^^^^^^^^^^^^^^^^ All leaderboard operations are **asynchronous**. Results are delivered via a Lua callback function; ``SteamAPI_RunCallbacks()`` is called automatically each frame inside ``onLoop``. .. data:: steam.findLeaderboard(name, callback) Asynchronously finds a leaderboard by name. :param string: **name** — leaderboard name as defined in the Steamworks partner dashboard (e.g. ``"HighScores"``). :param function: **callback(handle)** — called when the result is ready. ``handle`` is an integer (``SteamLeaderboard_t`` cast to int) on success, or ``nil`` on failure. :return: ``nil`` .. data:: steam.uploadScore(handle, score [, callback]) Uploads a score to a leaderboard using the *KeepBest* method (only improves the player's stored score). :param number: **handle** — leaderboard handle returned by the :data:`steam.findLeaderboard` callback. :param number: **score** — integer score to submit. :param function: **callback(success)** — *(optional)* called when the upload finishes; ``success`` is a ``boolean``. :return: ``nil`` .. data:: steam.downloadScores(handle, dataType, startRank, endRank, callback) Downloads leaderboard entries asynchronously. :param number: **handle** — leaderboard handle returned by the :data:`steam.findLeaderboard` callback. :param string: **dataType** — one of ``"global"``, ``"friends"``, or ``"around_user"``. :param number: **startRank** — 1-based rank to start from. :param number: **endRank** — 1-based rank to end at. :param function: **callback(entries)** — called with a table of ``{rank, score, name}`` records, or ``nil`` on failure. :return: ``nil`` *Example:* .. code-block:: lua :emphasize-lines: 2, 7, 13 steam.findLeaderboard("HighScores", function(handle) if not handle then return end -- upload current score steam.uploadScore(handle, 9999, function(ok) print("Upload succeeded:", ok) end) -- download top-10 global scores steam.downloadScores(handle, "global", 1, 10, function(entries) if entries then for _, e in ipairs(entries) do print(string.format("#%d %s %d", e.rank, e.name, e.score)) end end end) end) Cloud Storage (Steam Remote Storage) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ .. data:: steam.cloudWrite(filename, data) Writes a file to Steam cloud storage. :param string: **filename** — e.g. ``"save.dat"``. :param string: **data** — file contents as a Lua string (may be binary). :return: ``boolean`` — ``true`` if the write succeeded. .. data:: steam.cloudRead(filename) Reads a file from Steam cloud storage. :param string: **filename** — e.g. ``"save.dat"``. :return: ``string`` on success, ``nil`` if the file does not exist. .. data:: steam.cloudDelete(filename) Deletes a file from Steam cloud storage. :param string: **filename** :return: ``boolean`` — ``true`` if the delete succeeded. .. data:: steam.cloudFileExists(filename) Checks whether a file exists in Steam cloud storage. :param string: **filename** :return: ``boolean`` .. data:: steam.isCloudEnabled() Returns ``true`` only when **both** the user's account setting and the game's cloud setting are enabled. :return: ``boolean`` *Example:* .. code-block:: lua if steam.isCloudEnabled() then steam.cloudWrite("save.dat", savegame_data) end Overlay ^^^^^^^ .. data:: steam.activateOverlay(dialog) Opens a built-in Steam overlay dialog. :param string: **dialog** — one of: ``"achievements"``, ``"community"``, ``"friends"``, ``"store"``, ``"stats"``, ``"leaderboards"``, ``"officialgamegroup"``. :return: ``nil`` *Example:* .. code-block:: lua steam.activateOverlay("achievements") .. data:: steam.activateOverlayURL(url) Opens the Steam overlay with a web page. :param string: **url** — e.g. ``"https://steamcommunity.com/games/480"``. :return: ``nil`` .. data:: steam.showStore(appId) Opens the Steam store page for the given App ID inside the overlay. :param number: **appId** — Steam App ID. :return: ``nil`` DLC ^^^ .. data:: steam.isDlcInstalled(appId) Checks whether a DLC is currently installed. :param number: **appId** — the DLC's Steam App ID. :return: ``boolean`` — ``true`` if the DLC is installed. Full usage example ------------------ .. code-block:: lua local steam = require "steam" function onInitScene() if not steam.isReady() then print("Steam not available — skipping Steam features") return end -- Player info print("Player :", steam.getPlayerName()) print("App ID :", steam.getAppId()) print("Language:", steam.getCurrentLanguage()) -- Achievements steam.setAchievement("ACH_WIN_FIRST_GAME") print("Achieved:", steam.isAchievementAchieved("ACH_WIN_FIRST_GAME")) -- Stats steam.setStatInt("NumWins", steam.getStatInt("NumWins") + 1) steam.setStatFloat("FarthestDistance", 1234.5) steam.storeStats() -- Leaderboards (async) steam.findLeaderboard("HighScores", function(handle) if not handle then return end steam.uploadScore(handle, 9999) steam.downloadScores(handle, "global", 1, 10, function(entries) if entries then for _, e in ipairs(entries) do print(e.rank, e.name, e.score) end end end) end) -- Cloud saves if steam.isCloudEnabled() then steam.cloudWrite("save.dat", "my save data") local data = steam.cloudRead("save.dat") print("Cloud data:", data) end -- Overlay -- steam.activateOverlay("achievements") -- DLC print("DLC installed:", steam.isDlcInstalled(1234567)) end Security -------- .. Warning:: Never embed secret keys, server endpoints, or internal API keys in Lua scripts distributed with your game. Use mini-mbm's built-in AES encryption (``mbm.encrypt`` / ``mbm.decrypt``) to protect sensitive scripts before shipping. See the ``editor/asset_packager.lua`` tool for details. The following Steam data is **not** sensitive and safe to reference in Lua scripts: - App ID (public — visible in Steam URLs) - Achievement / stat API names (defined in the Steamworks partner dashboard) - Leaderboard names (public) Steam handles user authentication transparently. Players never enter credentials in the game. ``steam.isReady()`` returns ``false`` and fails gracefully if the client is not running or the user does not own the game. Troubleshooting --------------- +-----------------------------------------------+----------------------------------------------------------+ | Symptom | Likely cause | +===============================================+==========================================================+ | ``steam.isReady()`` returns ``false`` | Steam client not running, or ``steam_appid.txt`` | | | missing / contains the wrong App ID | +-----------------------------------------------+----------------------------------------------------------+ | Plugin fails to load | ``steam_api64.dll`` / ``libsteam_api.so`` not placed | | | next to the executable | +-----------------------------------------------+----------------------------------------------------------+ | Build fails — SDK headers not found | ``STEAMWORKS_SDK_PATH`` not set, or points to the | | | wrong directory | +-----------------------------------------------+----------------------------------------------------------+ | Achievements not appearing in Steam | Call ``steam.storeStats()`` after setting achievements | +-----------------------------------------------+----------------------------------------------------------+ | Leaderboard callback never fires | ``SteamAPI_RunCallbacks()`` must run each frame; | | | this happens automatically in ``onLoop`` | +-----------------------------------------------+----------------------------------------------------------+