Steam Rich Presence (Patreon)
Content
Last year Steam updated its friends list and chat client to support more features, and among them is support for "Rich Presence," or basically giving games running under Steam the option to share with friends more about the player's current state.
^My Cogmind Rich Presence status as seen in someone else's friends list while I was testing it out.
As you can see I've now added this feature to Cogmind for the next release :)
Exploring the potential for this feature had been on my TODO list for a good six months since I'd first heard about it, though it just sat there slowly sliding its way to the top as I ticked off higher priority features for Beta 9.
I don't normally use Steam as a consumer--I only started using their platform for development purposes, but not regularly enough to even notice Rich Presence in action, so that also made it less likely I'd promote the feature in the near term. Then more recently another player brought it up, and at the time I had a free evening and felt like doing a little mini-project, so I decided to give it a boost to the top...
Steam actually makes it extremely easy to do this (unlike Discord, which was also on my list but I dropped it earlier after doing some research, which is kinda funny since reportedly Steam finally rebuilt its friends/chat UI in response to competition from Discord...). You can read about Rich Presence in the Steamworks documentation, though I'll be covering its main features and my own experience with it here as well.
Content
First thing to consider is exactly what kind of text should be shown for the player's status in Cogmind?
Back when I initially jotted it down on my TODO list, the most obvious answer at the time was simply their location, as usual in a format like "-7/Factory." But with the launch of Beta 9 we also have some great new features that would be perfect here: build class and status summary! Originally devised for the purpose of helping contextualize mid-run stat dumps, classes also became a multi-section feature of the final scoresheet, and build class was even later expanded with an optional HUD display. Now these features can perform double/triple duty by including them in the rich presence string.
Why just show "-7/Factory " when we could show "Playing as Hacker-Skirmisher in -7/Factory. Status: Great"?
The Rich Presence possibilities now seem pretty colorful compared to showing only location, but will it all fit?
That was my next question, just how much room is there to display stuff, and unfortunately Steam's docs are actually not too clear on this point, just saying that if it's too long it'll be truncated or ellipisified (which are sorta two different things, the latter possibly implying it could be expanded and the former suggesting it can't, but anyway...). The docs don't get specific enough to base a real decision on, so I did a little research to see what would show for other games.
It turns out longer strings that don't fit are okay, since users can see the full string by expanding the friends list if necessary, although it's important to remember that users might only see the first 2~3 words unless their friends list is sized a little wider than the minimum allowed width. (You can see an example in my first screenshot above of the width required to show that string.)
Users who hover over a friend's avatar can also see the full details, which are just split up into multiple lines like so:
^Steam friend details with full multiline Rich Presence string.
So the first few words are going to be the most important in the string, and we could technically pack even more information in if we're willing to abbreviate. That said, while abbreviations are great for players already quite familiar with the game, I'd say Rich Presence is also useful in that it can be suggestive to friends who have never played the game, so non-standard abbreviations are best avoided.
As a traditional roguelike, most of Cogmind is spent controlling the main character rather than having a bunch of separate modes, so the vast majority of Rich Presence strings will simply be of the [build][location][status] variety. 99% of the playtime is covered there, and I ignored the intro/title for now (actually maybe I should just add an "Enjoying the animated intro" status for fun :P), so all that's left is the game over screen. Fortunately this didn't require a bunch of extra work because the new scoresheet header now includes the "Result" of the run as a string, which in addition to the "win result strings" we've had before was recently updated with even the specific cause of death. Behold:
^Steam friend details with cause of death while viewing the game over screen.
Implementation
The technical side of things is actually quite simple, it's almost just calling a single function to notify Steam of a new Rich Presence string, although as you'll see there's a bit more to it than that.
Before building proper architecture for this feature, I just wanted to call the SteamFriends()->SetRichPresence() function with some dummy text and see it working at a basic level. Other than the function call, doing this just requires one other step: Defining the available strings in a text file uploaded to the Steamworks backend.
Maybe they'll correct it at some point after I post this, but I discovered that the sample file in the general docs for this feature actually has a formatting error that prevents it from working. I found the proper format on the dedicated localization section of the docs.
Testing what was and wasn't working was fairly easy, however, since Steam provides a useful Rich Presence testing page (for devs who are logged in and playing their own dev build, even off Steam). It'll display any errors encountered while trying to load and set Rich Presence data.
^Using Steam's Rich Presence Tester page for devs.
This whole process was pretty quick, probably the only slight roadblock was figuring out what to put on the backend without having to upload a bunch of different strings--ideally everything should be handled in game. (The docs are focused more around localization and fairly modal games, rather than my own needs here.) Cogmind doesn't have localization, nor does it have a strong need to have a bunch of different "tokens" representing different modes, so I took a different approach: I have only one language, with one token, and it contains no text just a single variable :P
^Simple "localization" workaround for Rich Presence in Cogmind, that way I can just drop whatever string I want into the %text% variable and that'll be the player status.
If you're interested in how it's really done when you have multiple languages and more modes, check out the localization docs.
With that in place on the backend ready to accept non-static data, it's time for proper architecture...
^SteamRichPresence header (this should be a class rather than a struct but I'm lazy here since it's not going to hurt).
There's so little logic involved in setting a presence string that I started by simply updating it directly from where I wanted to check it in game, but after testing a bit discovered I'd need a second check elsewhere so, sure enough, going the smarter route of centralizing the code into its own thing was clearly the better option. So that simple class was born.
Technically it's ultimately faster to update this only when necessary, i.e. at the point of a change, but a centralized solution is much simpler to manage across multiple systems, checking every 5 seconds whether the current string should be updated or not. It Just Works, since all the relevant code is contained within the update() routine, and you don't have to worry about it elsewhere.
^SteamRichPresence update() method source.
The code is pretty straightforward: After figuring out what the string should be, if it's different from the most recently uploaded value, it loads the new string into the "text" variable, then writes the #StatusFull token (which as per the definition specified in the earlier file is nothing more than the text variable!) to "steam_display," which Valve has designated the target for displaying a Rich Presence string for the player.
After this it was just a matter of testing the different scenarios to confirm they actually work.
Rather than repeatedly going to the tester page or my friends list, once the data seemed like it was uploading normally I also implemented a little in-game indicator for debugging so I could more quickly see what Cogmind was sending to Steam:
^Testing Rich Presence updates with debug view active.
Once it's all working there's still one more bit: an option to turn it off! Rich Presence reporting is on by default, but maybe not everyone wants to advertise to their friends their awesome tank build, or sneaking into hidden maps, or winning, or... you know, the opposite of winning ;)
Whatever their reason, I added the disableSteamRichPresence setting in case players want it to just say regular old "Cogmind" like it always has!