Migration Guide from v4 to v5
v5 is a near-complete rewrite of the Lua bindings. The primary goal is to stay as close as possible to the C++ Steamworks SDK. This guide explains what changed since v4, and is useful to port your code, as it will require some manual work. It is recommended to read the Getting Started section as well.
The old API was hand-written and made many convenience choices (renaming fields, auto-dealing with buffer sizes). The new API auto-generates bindings directly from the API and mirrors the C++ signatures as closely as Lua allows. This may be less ergonomic to use, however, it scales better, we can support many more functions much more quickly. Implementing each function in v4 took a lot of work to analyze precisely what it does and how to make it as ergonomic as possible in Lua.
BENEFITS: |
More functions supported: The first release of v5 supported over 800 hundred Steam API functions (over 90% of all Steam API functions), while v4 supported only 122. |
Less mental overhead: Now the API works almost exactly the same as the Steam API, so you can just read that documentation and follow it, instead of having to constantly look up two distinct documentations. |
DRAWBACKS: |
Less ergonomic: Sometimes you have to deal with buffer sizes, bitmasks, and other C++-isms manually. |
Naming: Everything Is PascalCase
Every interface name and every function name is PascalCase, matching the C++ class and method names exactly.
Interface subtables
Old |
New |
|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
All other interfaces (Steam.UGC, Steam.NetworkingSockets, etc.) follow the
same rule.
Top-level lifecycle functions
Old |
New |
|---|---|
|
|
|
|
|
|
Fields in returned tables (structs in C++) now also use their exact C++ names, instead of the simplified ones from v4. For example, entry.m_nGlobalRank rather than entry.globalRank for LeaderboardEntry_t.
GameServer lifecycle (moved out of the interface)
Steam.GameServer.Init / Shutdown / RunCallbacks were moved to the top
level to mirror Steam.Init / Shutdown / RunCallbacks:
Old |
New |
|---|---|
|
|
|
|
|
|
Parameter, Function and Return Value Changes
Many functions now take a different number of parameters, or parameters of different types. Luasteam v4 aimed for ergonomics, so many useless parameters were removed (such as passing a table and also its size), while in v5 all parameters are required, so the conversion can be done automatically. The function return values might also be different, double check when migrating.
For example, UGC.getSubscribedItems() returned a table with the ids in v4, while UGC.GetSubscribedItems(cMaxEntries, bIncludeLocallyDisabled) in v5 takes a cMaxEntries integer with the size of the array to be allocated, which can be obtained from UGC.GetNumSubscribedItems(bIncludeLocallyDisabled), as well as taking an extra parameter bIncludeLocallyDisabled which took a default value in v4. Furthermore, it returns an integer as its first returned value (the size of the returned table), which was omitted in v4.
---- In v5 ----
local sz = Steam.UGC.GetNumSubscribedItems(false)
local _, items = Steam.UGC.GetSubscribedItems(sz, false)
registerItems(items)
---- In v4 ----
local items = Steam.UGC.getSubscribedItems()
registerItems(items)
In other times, some functions were fully omitted in v4, and used automatically under the hood, while in v5 they must be manually used. As an example, userStats.downloadLeaderboardEntries automatically called GetDownloadedLeaderboardEntry for you in v4, while in v5 you must call it manually.
---- In v5 ----
Steam.UserStats.DownloadLeaderboardEntries(handle, Steam.k_ELeaderboardDataRequestGlobal, 1, 1000, function(data, err)
if err then
print('Error happened')
else
for i = 0, data.m_cEntryCount - 1 do
local ok, entry = Steam.UserStats.GetDownloadedLeaderboardEntry(data.m_hSteamLeaderboardEntries, i, 0)
if ok then
local name = Steam.Friends.GetFriendPersonaName(entry.m_steamIDUser)
print('Rank #' .. entry.m_nGlobalRank .. ': ' .. name .. ' - ' .. entry.m_nScore)
end
end
end
end)
---- In v4 ----
Steam.userStats.downloadLeaderboardEntries(handle, 'Global', 1, 1000, function(data, err)
if err then
print('Error happened')
else
for _, user in ipairs(data) do
local name = Steam.friends.getFriendPersoneName(user.steamIDUser)
print('Rank #' .. user.globalRank .. ': ' .. name .. ' - ' .. user.score)
end
end
end
Enums and Constants
All C++ enum constants are exposed as plain integers on the Steam table,
using their exact C++ names. Thus, they can be accessed by using Steam.<CppConstantName>.
In v4, the enum values were not exposed anywhere, and functions that received enums often used strings with the enum name, now, they receive integers directly.
Old |
New |
Value |
|---|---|---|
|
|
4 |
|
|
2 |
|
|
8 |
When a function returns or a callback field contains an enum value, it is always a plain Lua integer. You can compare it directly to the constant:
if data.m_eResult ~= Steam.k_EResultOK then
print("Error:", data.m_eResult)
end
Bit Flags
Some API values are bitmasks — an integer where each bit represents an independent
boolean flag. GetItemState, GetPersonaStateFlags, and others work this way, either to receive bitmask parameter or return bitmask values.
In v4, these values were returned as tables with many booleans, while in v5 they follow the C++ format exactly and return integers. In LuaJIT, use the built-in bit library for bitwise
operations:
---- In v5 ----
local state = Steam.UGC.GetItemState(itemId)
-- state is an integer
local installed = bit.band(state, Steam.k_EItemStateInstalled) ~= 0
local needsUpdate = bit.band(state, Steam.k_EItemStateNeedsUpdate) ~= 0
---- In v4 ----
local state = Steam.UGC.getItemState(itemId)
-- state is a table with boolean fields subscribed, legacyItem, ...
local installed = state.installed
local needsUpdate = state.needsUpdate
Structs
Many Steamworks functions take or return C++ structs. luasteam v5 exposes these as Lua userdata with field access via metamethods, while v4 used different methods, tables or strings depending on which API method. See Structs on how to build and use them.
---- In v5 ----
local addr = Steam.newSteamNetworkingIPAddr {}
addr:ParseString("127.0.0.1:55556")
local opt1 = Steam.newSteamNetworkingConfigValue_t()
opt1:SetInt32(Steam.k_ESteamNetworkingConfig_TimeoutInitial, 1000)
local id = Steam.NetworkingSockets.ConnectByIPAddress(addr, 1, {opt1})
---- In v4 ----
local id = Steam.networkingSockets.connectByIPAddress("127.0.0.1:55556", {TimeoutInitial = 1000})
-- Note many types of options were not supported in v4 while all are in v5
Callbacks and CallResults
Callbacks and CallResults work very similar to v4, except for the names (and possibly types) of the fields in the callback structs, which now fully match the C++ name (e.g. m_eResult instead of result).
GameServer Variants
The initialization and shutdown of gameserver moved to be next to the core functions, so change Steam.gameServer.init(...) in v4 to Steam.GameServerInit(...) in v5. Also, now the interfaces that have a gameserver variant use acessors with the same name as in the C++ Steam API, so you can use Steam.GameServerUtils.XXX for the gameserver variant of Steam.Utils.XXX, for example.
Quick-Reference: Breaking Changes from v4
Area |
Old |
New |
|---|---|---|
Lifecycle |
|
|
Lifecycle |
|
|
Lifecycle |
|
|
Lifecycle |
|
|
Interfaces |
|
|
Methods |
|
|
Methods |
|
|
Extra |
|
|
Callback fields |
|
|
Leaderboard entries |
|
Returns |
|
no range args |
must pass |
|
|
|
|
returned table with |
returns integer bitmask; check with |
|
returned status as string |
returns integer |
|
|
|
Enums |
Used as strings |
flat on |