Steam
User identity, friends, achievements, stats, lobbies, game-server hosting, the in-game overlay, Cloud saves, Workshop, Remote Play, and rich presence. All wrapped over the Steamworks SDK.
local Steam = import("Steam")
if Steam.SteamPresent then
print("Hi, " .. Steam.User.Name)
Steam.Achievements.Unlock("FIRST_KILL")
Steam.Achievements.Store()
end
Steam.SteamPresent
Boolean. True if the Steam SDK redistributable
(steam_api64.dll / libsteam_api.so /
libsteam_api.dylib) is present alongside the
executable. That's the only thing it checks. Use
ruzit fetch-deps to drop the DLL in.
To make built games work standalone (no Steam launch required),
the runtime briefly writes steam_appid.txt next to the
exe at SDK init, lets Steam read it, then immediately deletes it.
The AppID is pulled from your build.toml [steam]
table, falling back to 480 (Spacewar) if unset.
The file appears for a few milliseconds during startup and never
lingers afterward — the AppID is effectively baked into the
runtime binary. The user's Steam client must still be running and
logged in for actual Steam API calls to succeed; if init fails,
individual Steam calls return / error accordingly.
If a steam_appid.txt is already present next to the
exe when the runtime starts (e.g., one you placed manually for
testing), the runtime leaves it alone — your file wins, and it
won't be deleted on shutdown.
Importing Steam never fails on its own anymore, so
you can keep the import at the top of any module. Just gate
real Steam calls behind Steam.SteamPresent:
local Steam = import("Steam")
local function on_kill()
if not Steam.SteamPresent then return end
Steam.Achievements.Unlock("FIRST_KILL")
Steam.Achievements.Store()
end
steam-app-id = N in build.toml
(or the RUZIT_STEAM_APPID env var). With
SteamPresent = true but no app id configured,
init falls back to a default and most APIs will still work in
test mode but won't unlock real achievements / friend data.
steam_api64.dll in the
runtime binary so it could be written next to the launcher at
startup. As of v3.7 you fetch it explicitly with
ruzit fetch-deps. This keeps the runtime
binary small and avoids redistributing the SDK without
a clear license trail.
Steam.OnDisconnected / OnConnected / OnConnectFailure
Top-level signals reporting the local Steam client's connection to the Steam backend. Affects every Steam-backed feature — lobbies, matchmaking, friends, achievements/stat sync, cloud, etc. — so this is the right place to globally pause networking when Steam goes offline.
Fires with a reason string when the Steam client loses its
connection to the Steam backend (network drop, Steam servers
down, etc.). Lobby data and friend lists become stale until
OnConnected fires.
Fires (no args) when the Steam client (re)connects. Refresh any cached lobby / friend / matchmaking state on this signal.
Fires with (reason, stillRetrying) when Steam fails
to reach its backend. If stillRetrying is
true, Steam will keep trying; otherwise it has
given up and you should surface the error to the user.
Fires with (lobbyId, friendId) when a friend
accepts a Steam invite to your game while it's already running
(typically by clicking Join Game in the Steam overlay).
Steam doesn't auto-join — you decide what to do. The usual
handler is to call Steam.Lobby.Join(lobbyId) and
switch your UI to the lobby/match flow. For cold launches
(game wasn't running when the invite was clicked), Steam puts
+connect_lobby <id> in the launch command
line — read it at startup via
Steam.Launch.GetQueryParam("connect_lobby").
If you only care about lobby disconnects, hook
SteamLobby.OnDisconnected on the specific lobby — it
distinguishes "Disconnected" from "Kicked" /
"Banned", and only fires for that lobby.
local Steam = import("Steam")
Steam.OnLobbyJoinRequested:Connect(function(lobbyId, friendId)
Steam.Lobby.Join(lobbyId):Connect(function(lobby, err)
if lobby then enter_match_with(lobby) end
end)
end)
-- Cold launch: game was offline when the invite was clicked.
local pending = Steam.Launch.GetQueryParam("connect_lobby")
if pending then Steam.Lobby.Join(pending):Connect(...) end
local Steam = import("Steam")
Steam.OnDisconnected:Connect(function(reason)
-- Pause matchmaking UI, show "reconnecting…" toast, etc.
print("Steam offline:", reason)
end)
Steam.OnConnected:Connect(function()
print("Steam back online")
end)
Steam.User
.ID | Steam ID string. |
.Name | Persona name. |
.Level | Steam community level. |
:AccountID() | Lower 32 bits of the Steam ID. |
:LoggedOn() | True while connected to Steam. |
:GetFriends() | { SteamFriend } snapshot. |
:GetAvatar(size?) | ImageAsset of your own avatar. |
:GetFriendAvatar(id, size?) | Friend / lobby member's avatar. |
:GetAvatarAsync(id, size?) | Signal-returning lookup that works for any Steam ID. |
:GetFriendInfo(id) | Persona state, nickname, current game. |
:RequestInfo(id, nameOnly?) | Force Steam to re-fetch profile data. |
:InviteToGame(id, connectString) | Send a Steam invite to your game. |
Steam.Achievements
name is the API id from the partner site, not the display
name. Always call Store() after a batch of unlocks.
.Unlock(name) | |
.Clear(name) | |
.IsUnlocked(name) | -> boolean |
.Store() | Push pending unlocks/stats to Steam. |
Steam.Stats
.SetInt(name, value) | |
.SetFloat(name, value) | |
.GetInt(name) | -> number |
.GetFloat(name) | -> number |
.Store() | Push to Steam. |
Steam.Lobby
Each entry returns a Signal that fires once with
the result. Connect once to handle the response.
.Create(maxMembers?, kind?) | -> Signal<SteamLobby?, string?> |
.Join(id) | -> Signal<SteamLobby?, string?> |
.List() | -> Signal<{ SteamLobbyInfo }> |
Steam.Lobby.Create(8, "FriendsOnly"):Connect(function(lobby, err)
if err then print("create failed: " .. err) return end
print("lobby " .. lobby.ID .. " up")
end)
Steam.Overlay
.Show(dialog) | One of the built-in panels. |
.ShowFriends() | |
.ShowAchievements() | |
.ShowSettings() | |
.ShowProfile(id) | |
.ShowChat(id) | |
.ShowWebPage(url) | |
.ShowStore(appId?) | |
.ShowInviteDialog(lobbyId) |
Steam.App
.IsAppInstalled(appId) | -> boolean |
.IsDlcInstalled(appId) | -> boolean |
.IsSubscribed() | -> boolean |
.IsVacBanned() | -> boolean |
.BuildId() | -> number |
.InstallDir(appId) | -> string |
.OwnerID() | -> string |
.CurrentLanguage() | -> string |
.AvailableLanguages() | -> { string } |
Steam.Cloud
Per-user file storage that auto-syncs across devices. Files are scoped to the calling user; quota is set in the partner site.
Returns true on success.
Nil if the file doesn't exist.
Both fields in bytes.
Steam.Workshop
Item ids the user is subscribed to.
Live install state. See WorkshopItemState.
Local install info; nil while not installed.
Live progress; nil if not downloading. Both in bytes.
Force-trigger a download. highPriority jumps the Steam queue.
Combined fetch: state flags + install info + a Files()
helper. See WorkshopItem.
Steam.Server
Spin up a Steam game server. Logon mode is set with
:LogOnAnonymous().
Steam.RichPresence
.Set(key, value) | Set a rich-presence k/v pair. |
.Clear() | Clear all keys. |
Steam.Launch
Metadata about how the current process started. Useful for handling "Join Game" deep links and for branching dev vs. shipped runs.
.GetCommandLine() | Steam URL launch params (+connect 1.2.8.4 +map dust). |
.GetQueryParam(key) | Pull a single +key value pair. |
.GetCurrentBeta() | Beta branch name or nil. |
.GetRunType() | Aggregate metadata table; see below. |
RunType payload
Source | "steam-url" / "steam" / "direct" |
WasLaunchedFromSteam | boolean |
CommandLine | string |
AppId | number |
BuildId / OwnerID / BetaBranch / InstallDir / Language | optional metadata. |
ProcessArgs | { string } |
Env | { [string]: string }, selected Steam-related env vars. |
Steam.Utils
.Country() | ISO country code of the local user. |
.AppCount() | Steam API level. |
Steam.RemotePlay
.Sessions() | { { UserID, ClientName?, Width?, Height? } } |
Object reference
Every type returned by Steam methods, in one place. The string-literal
types up top are pure aliases, "Public",
"Online", etc.
SteamLobby
Returned by Steam.Lobby.Create and Steam.Lobby.Join.
Holds member state, lobby-wide and per-member key/value data, and a chat
channel.
Lobby's Steam id (decimal string).
Owner's Steam id.
Fires with the joining member's Steam id.
nil when lobby-wide data changed; otherwise the
member id whose per-member data changed.
Fires when the LOCAL user is involuntarily removed from the lobby.
Reason is one of "Disconnected" (network drop /
Steam offline), "Kicked", or "Banned".
Does NOT fire when you call :Leave() yourself.
After this fires the lobby handle is inert.
Fires on every member (including the owner) when the lobby is
gracefully closed via owner:Close(). Use this to
show "host ended the lobby" UI. After it fires the local user
has already auto-left and the handle is inert.
Fires only on the kicked member when the lobby owner calls
owner:Kick(myId). Argument is the kicker's Steam
id. Use this to show a "you were kicked" popup. After it fires
the local user has already auto-left and the handle is inert.
Does NOT fire when you call :Leave() yourself.
Fires with (senderSteamId, bytes) when another
lobby member sends a P2P packet via :Send or
:Broadcast. Bytes are the exact payload the
sender passed in. Routed via Steam Networking Sockets
(NAT-friendly, encrypted, identified by SteamID — IPs are
never exposed). Messages from peers who aren't current
members of any of your lobbies are silently dropped.
True if the local user owns this lobby.
True once the lobby has been closed by the owner.
Snapshot of every member's Steam id.
Lobby-wide key/value store. Visible to every member. Keys
beginning with _ruzit_ are reserved by the
engine and will raise an error.
Delete a key from the lobby-wide store. Returns true if the key existed.
Full snapshot of every visible lobby-wide key/value pair.
Engine-reserved _ruzit_* keys are filtered out.
Configured maximum member count, or nil if Steam hasn't pushed the value yet.
Owner-only. Toggle whether new players can join via Steam.Lobby.Join.
Owner-only. Politely shut the lobby down: marks it unjoinable,
broadcasts a closed flag, fires OnClose on every
member (including the owner), and auto-leaves everyone. Use
this instead of :Leave() when the host wants to
end the session for everyone.
Owner-only. Kick a member by Steam id. The targeted client
fires OnKick and auto-leaves. Other members
observe the leave via OnMemberLeft. You can't
kick yourself — use :Close() or
:Leave().
Open Steam's invite-friends overlay for this lobby.
P2P send to one specific lobby member. Uses Steam Networking Sockets — relay-fallback, encrypted, NAT-friendly, addressed by SteamID (no IPs ever leaked). The connection is opened lazily on the first send to that peer (one handshake, ~100ms on the relay path, instant on direct LAN).
data is raw bytes — pass any string / buffer
holding your serialized payload. reliability
is one of "Reliable" (default, ordered, no-drop),
"Unreliable" (fast, lossy, no order),
"ReliableNoNagle" (reliable but flush
immediately), "UnreliableNoDelay" (drop instead
of buffer if the link isn't ready). Max ~1 MB per message.
Raises if memberId isn't a current member of
this lobby, or is yourself.
P2P broadcast to every other member of this lobby. Equivalent
to iterating :Members() and calling :Send
on each (skipping yourself). Same reliability semantics as
:Send.
Leave the lobby. The handle goes inert after this. If you're the owner and want everyone to leave, use :Close().
:Close() and :Kick() work under the hood.
Steam's matchmaking SDK has no native "close lobby" or "kick from lobby"
primitive. Ruzit implements both by writing reserved
_ruzit_closed and _ruzit_kick_<steamId> keys
to the lobby-data store. Every Ruzit client watches those keys and
auto-leaves / auto-fires OnClose / OnKick
accordingly. Non-Ruzit clients (third-party tools talking directly to
Steam matchmaking) won't honor the flags — kicks/closes only work
against players running your built game.
P2P networking via the lobby
:Send / :Broadcast / OnMessage are
backed by Steam Networking Sockets — Valve's modern P2P
transport with NAT punch-through, free relay fallback, end-to-end
encryption, and addressing by SteamID (peers never see each other's IPs).
Use this for actual gameplay packets; :SendChat is rate-
limited and only fits lobby-room chat.
local Steam = import("Steam")
Steam.Lobby.Create(8, "Public"):Connect(function(lobby)
-- Receive packets from any other member.
lobby.OnMessage:Connect(function(senderId, bytes)
print(senderId, "sent", #bytes, "bytes")
end)
-- Reliable (default) — ordered, no-loss. For chat, RPCs, state syncs.
lobby:Broadcast("hello everyone")
-- Unreliable — fast, may drop. For positions/inputs.
Heart:Connect(function()
lobby:Broadcast(serialize_player_state(), "Unreliable")
end)
end)
:Send to a given peer opens the P2P connection
(one round-trip handshake, ~100ms on relay, instant on direct LAN).
Connections are torn down automatically when a peer is no longer a
member of any lobby you're in. Incoming connections from peers
who aren't a member of any of your lobbies are auto-rejected, so
random Steam users on the same AppID can't spam your game.
SteamServer
Returned by Steam.Server.Start. The game-server side of the
Steamworks SDK.
Fires with the client Steam id when a client successfully authenticates.
Fires with a reason string when this hosted server's connection
to the Steam backend is lost. The server is no longer reachable
through Steam matchmaking until OnConnected fires.
Fires (no args) when the hosted server (re)establishes its connection to the Steam backend.
Fires with (reason, stillRetrying) when the
hosted server fails to reach the Steam backend. If
stillRetrying is true, Steam will
keep attempting; otherwise it has given up.
Fires when the server is being shut down via :Close() or :Stop().
Anonymous logon, no game-server-account credentials required.
Revoke a connected client's auth ticket
(EndAuthSession on the Steam SDK side). The
backend will no longer regard them as authenticated. Your
game's networking layer should also drop the underlying
socket / NetConnection — Steam's gameserver API only handles
authentication, not transport.
Gracefully shut the server down. Fires OnClose, then tears down the Steam server instance. Identical to :Stop(); pick whichever name reads better. Idempotent.
Stop and clean up. Fires OnClose. Idempotent.
SteamServerOptions
Plain table passed to Steam.Server.Start.
port | number?, game-server port. Default 27015. |
queryPort | number?, server-query (master-server) port. Default 27016. |
mode | SteamServerMode?, "NoAuthentication", "Authentication", or "AuthenticationAndSecure". |
version | string?, free-form version Steam uses for compatibility filtering. |
name / map | string?, display name and map tag for server browsers. |
max | number?, slot count. |
SteamFriend
Returned in lists by Steam.User:GetFriends().
ID | string |
Name | string, persona name. |
State | SteamPersonaState |
SteamFriendInfo
Returned by Steam.User:GetFriendInfo(id). Adds nickname + game.
ID | string |
Name | string |
Nickname | string?, the local user's nickname for this friend. |
State | SteamPersonaState |
Game | { AppID: number }?, populated when the friend is in a game. |
SteamLobbyInfo
Returned by Steam.Lobby.List, lighter-weight than a live SteamLobby.
ID | string |
MemberCount | number? |
Name | string?, whatever the host set as "name" in lobby data. |
SteamCloudFile
One entry from Steam.Cloud.ListFiles().
Name | string |
Size | number, bytes. |
WorkshopItemState
Returned by Steam.Workshop.ItemState(id).
Subscribed | boolean, user is subscribed to the item. |
Installed | boolean, install is on disk. |
NeedsUpdate | boolean, an update is available but not yet downloaded. |
Downloading | boolean |
DownloadPending | boolean, queued behind other Steam downloads. |
WorkshopInstallInfo
Returned by Steam.Workshop.InstallInfo(id) when the item is on disk.
Folder | string, absolute path to the install directory. |
SizeOnDisk | number, bytes. |
Timestamp | number, Unix epoch seconds of last update. |
WorkshopItem
Combined snapshot from Steam.Workshop.GetItem(id),
union of WorkshopItemState + WorkshopInstallInfo
+ a file lister.
ID | string |
Subscribed / Installed / NeedsUpdate / Downloading / DownloadPending | boolean, state flags. |
Folder | string?, absolute path to the install dir, when installed. |
SizeOnDisk | number?, bytes. |
Timestamp | number?, last-update Unix seconds. |
DownloadProgress | { Downloaded: number, Total: number }? |
Files() | (() -> { string })?, lists every file in the install folder. Absolute paths, pass straight to Asset.ImportAsset. |
RemotePlaySession
One entry from Steam.RemotePlay.Sessions().
UserID | string, remote user's Steam id. |
ClientName | string?, client device name. |
Width / Height | number?, stream resolution. |
RunType
Returned by Steam.Launch.GetRunType().
Source | "steam-url" | "steam" | "direct" |
WasLaunchedFromSteam | boolean |
CommandLine | string, Steam URL launch params. |
AppId | number |
BuildId | number? |
OwnerID | string? |
BetaBranch | string? |
InstallDir | string? |
Language | string? |
ProcessArgs | { string }, argv past the executable. |
Env | { [string]: string }, selected Steam-related env vars. |
String enums
| Type | Values |
|---|---|
SteamPersonaState |
"Offline" | "Online" | "Busy" | "Away" | "Snooze" | "LookingToTrade" | "LookingToPlay" |
SteamAvatarSize |
"small" (32×32) | "medium" (64×64) | "large" (184×184) |
SteamLobbyKind |
"Public" | "Private" | "FriendsOnly" | "Invisible" |
SteamServerMode |
"NoAuthentication" | "Authentication" | "AuthenticationAndSecure" |
SteamOverlayDialog |
"Friends" | "Community" | "Players" | "Settings" | "Stats" | "Achievements" | "OfficialGameGroup" |
SteamOverlayUserDialog |
"steamid" | "chat" | "jointrade" | "stats" | "achievements" | "friendadd" | "friendremove" | "friendrequestaccept" | "friendrequestignore" |
SteamOverlayStoreMode |
"None" | "AddToCart" | "AddToCartAndShow" |
SteamNotificationPosition |
"TopLeft" | "TopRight" | "BottomLeft" | "BottomRight" |