# Lobbycraft One set of Blueprint nodes for sessions, invites, achievements, stats and cloud saves, on Steam and on Epic Online Services (EOS). Unreal Engine 5.6, 5.7 and 5.8, Windows. The same game build runs under both stores. Started from Steam it uses Steam; started from the Epic Games launcher it switches itself to EOS. You don't branch on the store in your Blueprints. - [Quick start](#quick-start) - [Steam setup](#steam-setup) - [EOS setup](#eos-setup) - [Selling on both stores](#selling-on-both-stores) - [Nodes](#nodes) - [Config keys](#config-keys) - [The Doctor](#the-doctor) - [Dedicated servers](#dedicated-servers) - [Shipping builds](#shipping-builds) - [Testing](#testing) - [What was tested](#what-was-tested) - [Limitations](#limitations) - [Troubleshooting](#troubleshooting) ## Quick start This gets two players into the same map through Steam, using Valve's test app (480, "Spacewar"). No Steamworks account needed yet. 1. Enable the plugins in **Edit > Plugins**: Lobbycraft, Online Subsystem Steam, Steam Sockets. Restart the editor. 2. Add to `Config/DefaultEngine.ini`: ```ini [OnlineSubsystem] DefaultPlatformService=Steam [OnlineSubsystemSteam] bEnabled=true SteamDevAppId=480 bInitServerOnClient=true [/Script/Engine.GameEngine] !NetDriverDefinitions=ClearArray +NetDriverDefinitions=(DefName="GameNetDriver",DriverClassName="/Script/SteamSockets.SteamSocketsNetDriver",DriverClassNameFallback="/Script/OnlineSubsystemUtils.IpNetDriver") [/Script/SteamSockets.SteamSocketsNetDriver] NetConnectionClassName="/Script/SteamSockets.SteamSocketsNetConnection" ``` 3. Run **Tools > Lobbycraft Doctor**. It reads the project and lists what is wrong, with the line to paste. Fix until it says "No problems found". 4. In a Blueprint (a menu widget, the player controller): **Create Session** with your gameplay map on one side, **Find Sessions** then **Join Session** on the other. The map `/Lobbycraft/Example/L_LobbycraftExample` (plugin content) has all of it wired. 5. Package the game (Development). Steam does not work in Play In Editor; run the packaged build, with the Steam client open, on two PCs with two Steam accounts. For a first test on a single PC, tick **LAN** on Create Session and Find Sessions and start the packaged game twice. See [Testing](#testing). ## Steam setup ### Plugins | Plugin | Why | |---|---| | Online Subsystem Steam | Lobbies, friends, achievements, cloud | | Steam Sockets | The net driver that connects players through Steam's relays (no port forwarding) | ### DefaultEngine.ini The block in the quick start is the whole Steam configuration. Notes on it: - `SteamDevAppId`: your App ID once you have one. 480 is fine for sessions while developing, but it is shared with every other developer and it can't be used together with EOS. - `!NetDriverDefinitions=ClearArray` must come before the `+NetDriverDefinitions` line. Without it the engine's default IP driver stays first in the list and wins. - Don't use `/Script/OnlineSubsystemSteam.SteamNetDriver`. That class moved in 5.6; with the old path the game falls back to plain IP without saying so, and clients fail to travel. ### Achievements Steam only accepts achievements that are declared in the ini, by their Steamworks API name: ```ini [OnlineSubsystemSteam] Achievement_0_Id=ACH_FIRST_WIN Achievement_1_Id=ACH_TEN_WINS ``` They must also exist and be **published** in Steamworks (Stats & Achievements). If your game uses a different id from the Steamworks API name (because EOS uses another, say), map it: ```ini [Lobbycraft.SteamAchievementIds] FirstWin=ACH_FIRST_WIN ``` After each unlock Lobbycraft asks Steam whether the achievement really is unlocked. A name Steam doesn't know comes back on **On Failure** with "no such achievement in Steamworks (check the API name)". The engine alone reports success in that case. ### Stats Create the stat in Steamworks as **INT** and publish. Lobbycraft talks to Steam's stats API directly, because the engine's Steam subsystem has no stats interface. ### Cloud saves In Steamworks, open **Steam Cloud**, enable it and set both quotas: bytes per user and number of files per user. With either at zero, every save is refused. ## EOS setup EOS gives you the Epic Games Store side (achievements, stats, cloud, lobbies for players who bought the game there), and it can also run next to Steam. ### Plugins | Plugin | Why | |---|---| | Online Subsystem EOS | Everything EOS | | Socket Subsystem EOS | The EOS peer-to-peer net driver. EOS lobbies advertise the host by its EOS address, so joins need it | ### Developer Portal In the [Epic Developer Portal](https://dev.epicgames.com/portal), for your product: 1. **Product Settings > Clients**: create a client. Its policy must be **GameClient /w UnlockAchievements**. Plain "GameClient" can't unlock achievements. 2. **Epic Account Services**: create an application, link it to the client, and under Permissions turn on Basic Profile, Online Presence and Friends. 3. **Game Services > Achievements / Stats**: create them here. Stats used with Increment Stat need the **SUM** aggregation. 4. **Game Services > Player Data Storage**: nothing to create, but it needs the `ClientEncryptionKey` below. 5. Copy the ids from **Product Settings > SDK Download & Credentials**. Until Epic approves your brand settings, only members of your organization can sign in with an Epic account. Anyone else stops at a page saying "Application Access Is Restricted". To test with a friend, invite their Epic account to the organization (**Organization > Members**). ### Config In `Config/DefaultEngine.ini`: ```ini [OnlineSubsystem] DefaultPlatformService=Steam NativePlatformService=Steam [OnlineSubsystemEOS] bEnabled=true [/Script/OnlineSubsystemEOS.EOSSettings] DefaultArtifactName=MyGame bUseEAS=False bUseEOSConnect=True SteamTokenType=WebApi:epiconlineservices ``` For a game sold only on Epic, set `DefaultPlatformService=EOS`, leave `NativePlatformService` out, and skip the Steam sections. The credentials go in a file that stays out of source control, `Config/Windows/WindowsEngine.ini`: ```ini [/Script/OnlineSubsystemEOS.EOSSettings] +Artifacts=(ArtifactName="MyGame",ClientId="...",ClientSecret="...",ProductId="...",SandboxId="...",DeploymentId="...",ClientEncryptionKey="...") ``` - `ArtifactName` must equal `DefaultArtifactName`. - `ClientEncryptionKey` is 64 hexadecimal characters that you generate once and never change (it encrypts the cloud saves). In Python: `secrets.token_hex(32)`. - The field must be called `ClientEncryptionKey`. The engine strips a field named `EncryptionKey` from packaged builds, and cloud saves then break only after packaging. - Add the file to `.gitignore`. The Doctor warns when a file with the client secret is not ignored. ## Selling on both stores With the configuration above, one build covers both: | Started from | Sessions, friends, invites | Cloud saves | Achievements and stats | |---|---|---|---| | Steam | Steam | Steam Cloud | Steam, and EOS too if the player is signed in (see below) | | Epic Games launcher | EOS | EOS Player Data Storage | EOS | The launcher starts the game with `-AUTH_TYPE=...` on the command line; that is how Lobbycraft knows. On an Epic launch it turns on Epic account sign-in, makes EOS the default online subsystem and swaps the net driver to the EOS one, before the engine starts using them. Nothing to do in your project. If you would rather control those settings yourself, turn it off with `[Lobbycraft] bManageStoreSettings=False`. On an Epic launch, call **Login** once (at the main menu) before sessions, friends, stats or cloud. On a Steam launch no login is needed for the Steam side. ### Steam players signed in to EOS Optional. It lets a Steam player's achievements and stats also be written to EOS, without an Epic account. It needs your own Steam App ID (480 is rejected by EOS): 1. Portal: **Product Settings > Identity Providers > Add**: Steam, your App ID, and `epiconlineservices` as the SteamNetworkingIdentity. That text must match the part after `WebApi:` in `SteamTokenType`. 2. Portal: **Product Settings > Sandboxes > (your sandbox) > Identity Providers**: select the provider you just created for Steam. 3. Game: call **Login** after startup. ## Nodes All nodes are under the **Lobbycraft** category. The ones with a clock icon are asynchronous: they have **On Success** and **On Failure** pins, each with a text that says what happened. ### Sessions | Node | What it does | |---|---| | **Create Session** (Map, Settings, Max Players, LAN, Private) | Creates the session and opens Map as a listen server. Leave Map empty to stay where you are. | | **Find Sessions** (Filters, LAN, Dedicated Servers, Max Results) | Returns an array of Session Result. On Success with an empty array means the search worked and nobody is hosting. | | **Join Session** (Session) | Joins one result and travels to its host. | | **Update Session Settings** (Settings) | Host only. Changes the settings while the session runs. An empty value removes the key. | | **Leave Session** | Ends the session (host) or leaves it (client). Open your menu map yourself afterwards. | **Settings** are your own key/value texts: room name, game mode, map. Give them to Create Session; every Find Sessions result brings them back in its `Settings` map, and **Filters** on Find Sessions keeps only the sessions where the given keys have the given values. Keys may use letters, digits and underscore, and they come back in UPPER CASE, because the stores don't keep the case. A **Private** session is left out of searches. Players get in by invite or by joining a friend from the store's friends list. **Session Result** fields: Host Name, Players, Max Players, Ping Ms, Dedicated, Settings. One operation of each kind runs at a time. A second Create while the first is pending fails with "Busy". When the host leaves or the connection drops, the client's session is closed for you, so the next Create or Join works. Sending the player back to the menu is up to your game (the engine's network failure event). ### Invites and friends | Node | What it does | |---|---| | **Get Friends** | The friends list, each with Display Name, Online and Playing This Game. On EOS, call Login first. | | **Invite Friend** (Friend) | Invites one friend from Get Friends to the current session. | | **Show Invite Overlay** | Opens Steam's invite dialog. On EOS it opens the friends overlay, and the player invites from there. | | **Set Presence** (Status) | Sets the player's status text. | When a player accepts an invite, or picks "Join Game" on a friend, Lobbycraft leaves the current session and joins the new one by itself. To decide that in your own code (to ask "leave this match?", for instance), set `[Lobbycraft] bAutoJoinInvites=False` and bind **On Invite Accepted** on the Lobbycraft Session Subsystem; it hands you the Session Result to pass to Join Session. On Steam, the friends list only shows custom status text through a rich presence localization file in Steamworks. Set Presence stores the text under the rich presence key `status`; reference it from your localization tokens. ### Account | Node | What it does | |---|---| | **Login** | Signs the player in to EOS: with the Epic account on an Epic launch, with the Steam account on a Steam launch. On Success brings the EOS user id. | | **Get Launch Store** | Steam or Epic. For the rare place where you do need to know. | ### Achievements and stats | Node | What it does | |---|---| | **Unlock Achievement** (Achievement Id) | Unlocks on every store the game is running under. | | **Increment Stat** (Stat, Amount) | Adds Amount to an integer stat on every store the game is running under. | Both succeed when at least one store accepted the write, and the text lists each store's answer, one after the other (`steam: ...; eos: ...`). ### Cloud saves | Node | What it does | |---|---| | **Save Game To Cloud** (Save Game, Slot) | Stores a Save Game object in the cloud of the store that started the game. | | **Load Game From Cloud** (Slot) | Brings it back. Cast the result to your Save Game class. | It is the same Save Game object you would pass to the engine's Save Game To Slot. Saves are per store: a save made under Steam is not visible under Epic. ### C++ The nodes are thin wrappers over three game instance subsystems, which you can call directly: `ULCSessionSubsystem`, `ULCAchievementsSubsystem`, `ULCCloudSubsystem`, plus `ULCSocialLibrary`. Add `"Lobbycraft"` to your module's dependencies. The headers are commented. ## Config keys All optional, in `Config/DefaultEngine.ini`. ```ini [Lobbycraft] ; Let Lobbycraft switch the project to EOS when the Epic launcher starts the game. Default True. bManageStoreSettings=True ; Join by itself when the player accepts an invite. Default True. bAutoJoinInvites=True ; A dedicated server advertises itself at startup. Default True. bAutoRegisterDedicatedServer=True ; Player slots of that dedicated server. Default 16. DedicatedMaxPlayers=16 ; Its name in the server list. Default: the project name. DedicatedServerName=My Server [Lobbycraft.SteamAchievementIds] ; Your achievement id = the Steamworks API name, when they differ. FirstWin=ACH_FIRST_WIN ``` ## The Doctor Most online problems in Unreal are configuration problems that fail silently. The Doctor finds the known ones. **In the editor: Tools > Lobbycraft Doctor.** It checks the project files and opens a Message Log page. Each finding has the problem and the exact fix. Among others: Steam enabled but never started, no App ID, App ID 480 together with EOS, achievements not declared, the net driver that moved in 5.6, a duplicate net driver definition, missing or incomplete EOS credentials, the encryption key field that is stripped from packaged builds, an EOS secret that is not in `.gitignore`, a package without the prerequisites installer. Lines marked Info are portal steps the Doctor can't check from your PC. **In the running game.** In Development builds, when the first map loads, Lobbycraft compares what is configured with what actually started, and shows a message on screen and in the log (prefix `LCDOCTOR`) if they differ: Steam configured but not running, the net driver that fell back to IP, the player not signed in to Steam. Run it again at any time with the console command `Lobbycraft.RuntimeDoctor`. ## Dedicated servers A dedicated server registers itself when it starts; clients list it with **Find Sessions** and Dedicated Servers ticked. Size and name come from the [config keys](#config-keys). On Steam, the engine's own dedicated server registration never completes on 5.6 to 5.8 (it times out after 15 seconds). Lobbycraft does the registration itself, in the format the engine's server search reads, so nothing changes on the client side. Things to know: - Building a Server target needs an engine compiled from source. The launcher engine refuses. - The public Steam server list reaches the server from the internet on UDP 27015. Behind a home router that port must be forwarded. - A client and a dedicated server on the same PC fight over the Steam ports. Start that client with `-ini:Engine:[OnlineSubsystemSteam]:bInitServerOnClient=false`. See [What was tested](#what-was-tested) for how far dedicated servers have been proven. ## Shipping builds - **`steam_appid.txt`.** Development builds write it next to the executable from `SteamDevAppId`. Shipping builds don't. A Shipping build started outside Steam, with no `steam_appid.txt`, runs with no Steam at all and no error anywhere. To test a Shipping build before uploading it to Steam, create `steam_appid.txt` (one line, your App ID) next to the game executable in `<Game>/Binaries/Win64/`. Don't ship that file. Players start the game from Steam, which doesn't need it. - **No log.** With the launcher engine a Shipping build writes no log file, and ignores `-ExecCmds` and `-ini:` on the command line. Do your diagnosis in a Development package. - **Console commands.** The `Lobbycraft.*` test commands are compiled out of Shipping. The in-game Doctor is Development only too. - **`UE_PROJECT_STEAMSHIPPINGID`.** If your `Target.cs` defines it, a Shipping build started outside Steam restarts itself through Steam before any plugin loads. An Epic build of the same game must not define it. ## Testing **One PC, no second account.** Tick LAN on Create Session and Find Sessions and start the packaged game twice. With Steam running, start the second copy with `-ini:Engine:[OnlineSubsystemSteam]:bInitServerOnClient=false`, or start both with `-nosteam` to test without any store. **Two accounts.** Online sessions and invites need two Steam accounts on two machines (a virtual machine works). Two copies of the game on one PC share one Steam account and can't see each other's lobby. **Epic, without publishing to the store.** Start the packaged build with `-AUTH_TYPE=accountportal`. Login then opens the browser for the Epic sign-in. Remember that only organization members get through until the brand review. **Console commands** (Development builds, key `~`): | Command | | |---|---| | `Lobbycraft.Host <Map> [Max] [lan] [private] [Key=Value ...]` | Create Session | | `Lobbycraft.Join [lan] [dedicated] [Key=Value ...]` | Find Sessions, then join the first result | | `Lobbycraft.Update Key=Value` | Update Session Settings | | `Lobbycraft.Leave` | Leave Session | | `Lobbycraft.EOSLogin` | Login | | `Lobbycraft.Unlock <Id>` / `Lobbycraft.Stat <Name>` | Unlock Achievement / Increment Stat | | `Lobbycraft.Friends` / `Lobbycraft.Invite <NamePart>` / `Lobbycraft.InviteOverlay` / `Lobbycraft.Presence <Text>` | Friends and invites | | `Lobbycraft.RegisterServer` | Register a dedicated server by hand | | `Lobbycraft.RuntimeDoctor` | Run the in-game Doctor | Every operation writes one line to the log starting with `LOBBYCRAFT`, with the result and the reason. ## What was tested Everything below ran in **packaged builds**, not in the editor. Automated on every release, on UE 5.6, 5.7 and 5.8 (Windows, Development package, Steam app 480), 23 checks per engine version: - Steam: increment stat, unlock achievement (confirmed with Steam), wrong achievement name refused, cloud save and load, create session, update settings, friends list, presence, leave, in-game Doctor clean. - Steam LAN through Steam Sockets, and LAN with no store: host listens, filters, settings reach the client, find and join, host accepts the player, private session not listed. By hand, two machines: | | 5.6 | 5.7 | 5.8 | |---|---|---|---| | Steam online join, two accounts | yes | yes | yes | | Steam: invite into a private session, auto-join, host drop | | | yes | | Shipping build as host and as client (Steam) | | | yes | | Epic sign-in, EOS stats, achievements and cloud | | | yes | | EOS lobby: host, find, join between two Epic accounts | | | yes | | Steam player signed in to EOS, achievements and stats written to EOS | | | yes | An empty cell means not run yet on that version, not that it fails. ## Limitations - Windows only. - Dedicated servers: registration is proven (the server answers Steam's server query with the right data, and the LAN round trip passes). Joining through the public Steam list from another network, and dedicated servers on EOS, have not been tested end to end. - EOS friends list and EOS invites have not been tested end to end. EOS has no invite dialog of its own in the engine; Show Invite Overlay opens the friends overlay instead. - Stats are integers, and the only operation is adding. No leaderboards. - Cloud saves are not shared between stores. - One local player (no split screen sign-in). - No voice chat, matchmaking queues, or lobby chat. - Steam does not work in Play In Editor (an engine limit). In the editor the nodes run on the Null subsystem, which is enough to test LAN flows. ## Troubleshooting **Nothing works in the packaged game, and Steam's overlay doesn't open.** Steam did not start. Is the Steam client open and signed in? Is `steam_appid.txt` next to the executable (Development writes it, Shipping does not)? Was the game started with `-nosteam`? The in-game Doctor names the cause. **Find Sessions succeeds with zero results.** On app 480 this is common: search both PCs from the same region, with different Steam accounts, and make sure the host really is in the session (`LOBBYCRAFT create ok` in its log). A private session never shows up. Two different builds of the game don't see each other either; the engine filters by build id. **The client finds the session and the join fails to travel.** The two sides use different net drivers. Run the editor Doctor: it is usually the `ClearArray` line missing, or Steam Sockets disabled. **Unlock Achievement fails with "no such achievement in Steamworks".** The name isn't the Steamworks API name, the achievement isn't published, or it isn't listed under `Achievement_N_Id`. **Save Game To Cloud is refused on Steam.** Steam Cloud is off for the app, or a quota is zero or full (bytes or number of files). **Epic sign-in stops at "Application Access Is Restricted".** The account isn't a member of your organization and the brand review isn't done. **Steam players can't sign in to EOS.** App ID 480 can't; use your own. Then check the identity provider (App ID, `epiconlineservices`) and that it is selected in the sandbox. **The game started twice on one PC, and the second copy has no Steam.** Both tried to open the Steam game server ports. See [Testing](#testing). Support: contato@tessaroapps.com