Official development blog
[ Latest Cogmind Release Notes: Feb 2026, "Unchained More" ]

Leaderboards and Player Stats with Protobufs

After finally ripping out the old leaderboard and metrics system, it’s time to build a new one!

I’d technically already completed the first significant chunk of the new system months before, described in the Ultimate Roguelike Morgue File series. In particular the “Preparation” section in Part 1 describes how I created an external file to store all the scoreTypes and their associated parameters. This will again become important later in the process.

Now as much as I love the new text format for scoresheets, we’re certainly not going to be uploading text files anymore. The new online data system needs to be elegant, more compact, more secure, and easier to manage overall, not just a bunch of text files that are uploaded by players then batch downloaded and manually run through a dedicated analysis program :P

Protocol Buffers

The first step on this new route is to decide what format to store the scoresheet in. Technically Cogmind’s score data already has its own binary format, a well-organized one that I use to keep it in memory and, of course, compressed in the save file itself. And it works well for this purpose, but it’s just my own format, and therefore not widely compatible or as easily usable outside Cogmind. So we need an additional format for scoresheet data, one that will be more suitable for upload, storage, and manipulation.

Will Glynn, the networking pro helping me with the server side of this new adventure, suggested we use protocol buffers, also known as “protobufs.” Protobufs are apparently a popular solution for this sort of thing (and storing data in general!), with a number of features we’ll find useful:

  • Protobufs are a compact binary format, which is important considering the sheer amount of values stored in the scoresheet for a single run--up to tens of thousands of data points and strings!
  • Protobufs can be easily converted to other formats like JSON (in fact they’re kinda like binary JSON data to begin with), and be parsed by a bunch of existing libraries for analysis or other uses, not to mention the accessibility benefits of support across many languages.
  • Protobufs are designed to be easily extensible while remaining backwards and forwards compatible. As long as no data identifiers are changed, we can add new data to later versions of the game and these scoresheets can be uploaded normally, even if the server doesn’t yet understand them--the ability to interpret new data can be added later if desired, and the data will still be there all the same. This makes it easier for me to put out new versions that extend the scoresheet without resetting the leaderboards, which I couldn’t do before since the analyze_scores.exe I talked about last time, used to build the leaderboards and produce stats, expects everything to remain unchanged from when a particular version was released. (This is why the leaderboards prior to Beta 9 were only ever reset with major update releases, and scoresheet updates always had to be postponed until that happened, even if minor updates could have benefited from including new entries.)

Setting up Protobufs

Protobufs it is, so let’s set it up! The docs are pretty good, and include API references and tutorials in a range of different languages, including C++ (which I’m using).

The first step is to download the library from GitHub, and in my case I had to use a slightly older version, the last to support Visual Studio 2010, since that’s what I’m still using to develop Cogmind. It includes a readme with setup instructions for both the protobuf library against which to link the game, and for “protoc,” the protobuf compiler, which is essentially used to read the game’s specifications for the protobuf content and generate a header-source pair of files for reading and writing the data from and to that format.

The data format is initially defined in one or more text files with the “.proto” extension, and anyone with that file can read and write a proper protobuf scoresheet. Cogmind just has the entire scoresheet data for a single run defined in one 1,811-line file.

cogmind_scoresheet_proto_excerpt_route

An excerpt from Cogmind’s scoresheet.proto. Notice the ID numbers associated with each entry. Once set and used, those can’t change if you want to retain compatibility across versions, since from that point on they identify the specified data type. Protobufs support structures (“messages”), nested structures, enums, and common data types like integers, booleans, strings, and more.

Protoc reads that file and turns it into code, altogether generating functions totaling 2.85 MB. And that’s with the .proto file specifying “option optimize_for = CODE_SIZE”! This results in lighter code at the cost of runtime performance, but Cogmind isn’t making heavy or wide use of Protobufs, just for a one-time upload of the run data, so tight performance isn’t a requirement. (Out of curiosity I compared the results, and without CODE_SIZE the protobuf files come out to 5.34 MB. Cogmind’s entire source itself is only 7.79 MB xD)

cogmind_source_protobuf_proto_generatedpng

An excerpt from the 22,948-line pb.cc file generated by Protoc, corresponding in part to the .proto file section shown before.

Loading Protobufs with Game Data

We have a format, we have the code, now to put the real data in the format using that code :)

Again the docs came in handy, although there’s more than one way to do it and the tutorial doesn’t cover them all so I tested several approaches and eventually also looked around at several other websites for some use cases and inspiration before settling on a final method.

In my own projects I like to use enums for everything, making it easy to write code for loading, moving, comparing, and retrieving large amounts of data, but protobufs have a separate set of functions for every. single. piece. of data. xD

This complicates things a bit, and at first I tested the idea of using macros to set values by retrieving a pointer to mutable protobuf data, or assigning a new protobuf object, where the macros are used to call the necessary function names.

cogmind_source_protobuf_loading_tests

Testing different methods of using the protobuf API to load values.

In both cases those code sections are merely populating values for the relatively simple Performance section of the Cogmind scoresheet, which you can see below.

cogmind_scoresheet_proto_excerpt_performance

Cogmind scoresheet.proto excerpt defining Performance data, and an inset demonstrating what that message represents in the scoresheet itself.

Looking back at the macro-powered source, either of these methods works fine if there’s a little bit of data--who cares, just get it done, right? But we’re going to need to also come up with a more efficient way to do this, one that doesn’t involve writing a ton of code for the huge amount of data needed to represent a scoresheet.

Taking the scoresheet’s Bonus section as an example, it currently contains 74 entries…

cogmind_scoresheet_proto_excerpt_bonus_partial

Cogmind scoresheet.proto partial excerpt of Bonus message.

Writing out all the code to load all these, even if just one line each with the help of macros, is kinda excessive, not to mention having to update them any time new ones or added or existing ones are modified in the game. And this is just one section! What if we could just have it update automatically any time there are changes?

There’s a much easier way that relies on the fact that I already have both enums and strings associated with all of these entries: Reflection. It’ll be slightly slower, but again the performance aspect is negligible because loading protobufs is not something the game is generally doing.

This bit of code loads the entire Bonus section, and doesn’t need to be touched in the future even if the section is expanded with more entries:

cogmind_source_protobuf_loading_reflection

Loading a scoresheet protobuf via reflection.

FindFieldByName() is our friend, a protobuf method that does exactly that, and I just have to feed it the right name based on the scoreType in question, properly converted from my own string format to that required by the protobuf API via protobufFieldName().

This approach works great for the majority of scoresheet sections, but unfortunately there’s still one (rather big) roadblock: the stats section storing separate sets of data for every map.

Now technically if I stuck to having the scoresheet.proto format reflect the internal data structures, all of the stats could’ve easily been handled in the same manner, and if it was just me working on this project that’s probably how it would’ve ended up, but Will convinced me that for the online system we needed to store the run data differently than I was doing it internally.

In short, this meant the stats section is going to need… a lot of code. Fortunately all of the relevant scoresheet data is defined in the game’s external text file described in Building the Ultimate Roguelike Morgue File, Part 1: Stats and Organization, so it can be used to generate the code to convert it. Yay, more generated code xD

So it turns out that part of scoresheet.proto--the messages related to general per-map stats, is generated by a script, and the source to load it at run time is also generated.

cogmind_source_protobuf_loading_stats_generated

Excerpt from the 762-line generated source file that loads all per-map stat-related messages and their submessages (and sometimes submessages of those submessages!) based on the game stats recorded at the time.

Here I should point out that all this last-minute large-scale conversion stuff could’ve been avoided if Cogmind simply used protobufs natively for all of the scoring data and stats rather than a separate format, which is what most people would probably do with protobufs. With that approach, when it comes time to upload everything is already packaged and ready.

There are a number of reasons I didn’t do that:

  • For internal use I prefer a stat-first organization over what was decided would be best for the online data (route-first and embedding a lot more meaning into the format structure itself).
  • It would’ve been far more work to go back and change how everything works, as opposed to just converting the existing data when necessary once at the end of a run.
  • I didn’t want the protobuf library to “invade” the rest of the code, so as an optional part of the architecture it can easily be disabled or removed.
  • The internal scoresheet data was originally built and maintained to create the text version of the scoresheet, so it’s more suited to that purpose than using the protobuf content as a starting point.

But anyway, that’s just me and this project. I’d still recommend protobufs as a pretty good solution to serve as the foundation for this sort of thing, or potentially even entire save files depending on the game!

Uploading via WinINet

Last time I described how for years I’d been using SDL_net to upload scoresheet files, but that’s not gonna cut it anymore. I defer to Will on networking matters where possible, and he said we need HTTPS, so we need HTTPS… Just gotta figure out how to do that xD. He listed some options to get me started, though for me it’s always a huge pain (or complete roadblock) to simply add libraries and get basic stuff actually working at all, so I wasn’t too confident in my ability to implement them.

gRPC

One seemingly particularly good option would be gRPC, since it’s been built to integrate with protobufs in the first place. Unfortunately it’s not compatible with Windows XP, which Cogmind still supports, and I think it would also require upgrading my project to VS2015 or so. While it’s possible I’ll eventually drop XP support anyway and probably one day upgrade to a newer version of VS, there’s no reason to do those things now if there’s a workable alternative, so I passed on this option.

libcurl

I was familiar with libcurl from my research years back before deciding on SDL_net, plus I’ve heard it recommended by other devs, but setting it up was nowhere near my idea of easy. Although I got pretty far in the process, it seemed that for SSL support I also needed to include OpenSSL, which is built against VS0217. Regardless, I eventually got it down to only one remaining linker error that I didn’t even understand, and ended up giving up on libcurl.

I’m also generally against adding a bunch more heavy DLLs to the game, especially for one little feature, another strike against libcurl. (Adding just protobuf support already increased the size of COGMIND.EXE by 15% xD)

WinINet

What better option than to cheat on download size and use native Windows DLLs? :P

Will couldn’t recommend WinINet, but I’m glad he listed it as a last resort because it’s right up my alley. Windows is good with backwards compatibility, so that also makes it more likely that I’d be able to use it easily despite using relatively old tech compared to so many of the other networking libraries out there. Remember, I’m not writing some persistent online application, or trying to do any kind of multiplayer networking--all I want is the occasional HTTPS data transfer, so it would seem kinda ridiculous to use anything more than the bare minimum necessary to achieve that goal.

For this option all I had to do was simply link “Wininet.lib” and start writing code! Love it :)

Well, the code part wasn’t as straightforward as I’d like. As with SDL_net, how to actually use WinINet properly took a little while to figure out, even more so since we needed SSL. The example in the docs was a helpful start, but didn’t cover everything I needed to know in order to conduct a successful transfer. I eventually came up with workable code by mixing and matching various solutions found on the net.

Of course it needs a nice C++ wrapper to simplify everything:

cogmind_source_wininet_https_connection_header

The header for a simple C++ class capable of handling HTTPS connections, along with a sample of how to use it (as of now that test will actually retrieve the latest Cogmind version number and news). The full header can be downloaded here, and the source here (file extension needs to be changed to .cpp--it was changed for hosting due to security reasons).

Altogether it’s less than 200 lines, and in addition to that test above, as the class was developed I ran a series of simple incremental tests until eventually reaching the point where I could encode and upload an entire scoresheet in protobuf form. (I’ll show an example of this further below when we get to crypto signing.)

Storing the Data Online

These scoresheets are uploading… to where, now? I can’t get into a lot of detail here, because much of it is currently a black box to me. The network side is Will’s area, but here are the basic steps I needed to finish hooking everything up:

  1. On gridsagegames.com I created a new subdomain that points to a Heroku service. This is easily done in cPanel with a simple CNAME record.
  2. I set up an AWS account which is where the Heroku service built by Will stores all the uploaded scoresheets for processing/reference, in an AWS S3 bucket.

For me, from there it was just a matter of using HttpsConnection to send a scoresheet and watch it appear in the bucket \o/

cogmind_AWS_data_first_scoresheet_upload

The first ever Cogmind scoresheet successfully uploaded to AWS S3, as seen in the console.

Although there probably won’t be any issues with the data on AWS, you never know what can happen and backups are essential, so in exploring backup options I found I can use the AWS CLI to download everything in the bucket to my local computer (instructions), from where it would also be included in my own automated offsite backups.

cogmind_s3_server_data_download_CLI

Downloading Cogmind score data via AWS CLI.

I had just set up a script to do that automatically every day, and of course I mention this to Will and he immediately writes a program to both do this as well as limit downloading to only new files, and zip everything up xD

cogmind_s3_server_data_download_program

Downloading Cogmind score data via AWS CLI. This was about a week after Cogmind’s first public version including the new scoresheet system.

Additional Features

Since the leaderboards based on this new system weren’t ready when Beta 9 released, but in the meantime I wanted people to at least have a chance to see the new data as it appears online (while being able to easily share their complete run info without sending a file), I had the idea to append URLs for their run data on the server to the end of their scoresheet once it was successfully uploaded.

cogmind_scoresheet_urls

URLs for a run’s data, in TXT and JSON formats, as appended to the end of a scoresheet.

See a live scoresheet sample here in text format, which is identical to the local version, and the same scoresheet data here as JSON, which essentially mirrors the protobuf format.

Depending on your browser, the JSON file might appear as a barely readable mess, or if your browser knows what it’s doing and has built-in functionality for parsing them there will be formatting options for readability and/or interaction.

cogmind_scoresheet_json_data_sample_firefox

Displaying the sample run data in Firefox in raw JSON, Pretty Print, and the interactive view.

Reuploading

One of the features missing from the old system which I kept waiting for the new system to actually implement is how to avoid losing potential run data. What I mean are those times that the website server happens to be unreachable for some reason, or maybe the player is currently offline or their own internet connection has issues. From then on the only way that score/data could ever make it to the leaderboards/stats is if they gave me the file and I manually uploaded it, which I did do for special runs by players on request, but now that we have the real, final system, this calls for something automated.

In cases where the player has uploads activated in the options but the process can’t be carried out or fails for some reason, the complete protobuf data is archived in a temporary subdirectory in their local scores folder. Each time Cogmind starts up, it checks that directory for scores that failed to upload, and tries them again. This saves time, saves headaches, and ensures we have all of the runs that we should! Now I won’t feel nearly as bad if the service is inaccessible for some reason :)

Cryptographic Signing with libsodium

My original dumb I-don’t-kn0w-networking approach for identifying the player associated with a given scoresheet was going to be to upload that scoresheet’s data alongside their player GUID, which would not be part of the public protobuf format. As a private value, it would prevent a malicious actor from spoofing another player and messing up their historical data, and our aggregate data along with it.

Unfortunately, keeping this unique identifier private would also decrease the accuracy of third-party data analysis (which, as we’ve seen, is something a number of Cogmind players like to do!).

At Will’s suggestion, however, there’s a way to add the same extra layer of security without hiding any relevant scoresheet values: have players sign their uploads with a local private key! That enables us to safely ban specific players as necessary since it’s no longer possible for someone to pretend they’re someone else, making it easier to ensure the integrity of the leaderboards and stats.

So what does it actually mean to “sign” something… I had no idea, either, but the libsodium docs explain it all pretty well, including examples of how to do it. Super simple. libsodium is definitely my type of library! Just drop it in, write a few lines of code following their examples, and enjoy the results.

Players store their own private key locally, and the public key (more or less like a GUID, insofar as it’s a unique identifier) is included with the protobuf along with a signature created based on the private key and the data to be transferred. The online service checks that the provided public key properly matches the data signature, to confirm that the data was, indeed, uploaded by the player they say they are, and then add that data to the AWS S3 bucket. If a data transfer fails that check, it’s ignored.

Here’s the code for using HttpsConnection from earlier to upload a signed protobuf scoresheet. It took me a while to figure out how to get the protobuf data in the right format for transfer over HTTP, so maybe this will help someone in the future:

cogmind_source_scoresheet_uploading

The function for uploading a signed Cogmind scoresheet to the server, using libsodium and the WinINet wrapper from earlier..

Also note that as per the libsodium docs there are two different ways to use signing, either “combined” or “detached.” The former actually inserts the signature at the beginning of the data itself, whereas detached mode allows you to send the signature separately. The sample use case above is using the detached mode, inserting the key in the HTTP header instead of messing with the protobuf.

And there you have it, the gist of Cogmind’s new handling for network-related stat stuff! It’s been a long time since I added someone to the Cogmind credits, but I couldn’t have done this without Will, so there he is :)

cogmind_credit_menu_leaderboards_rework

Cogmind credits page, 2019 version.

The AWS web interface itself only allows us to get a glimpse of what’s there (psh, viewing 1 to 300…), but there are thousands since the Beta 9 release.

cogmind_AWS_data_lots

Browsing the collection of Cogmind scoresheets in the AWS S3 web interface.

Next up is the new leaderboards based on all this data… And I’m still not sure how I’ll be doing the usual data visualization and statistical analysis now that everything is in a new format, but I’m sure it’ll work out.

Posted in Gamedev | Tagged , , , | Leave a comment

Leaderboards and Player Stats: The Old Way

It’s the end of an era! From May 2015 through October 2019, Cogmind used the same architecture for score uploading and leaderboard compilation that I’d patched together a few months before releasing the first alpha. That’s all changing as of Beta 9 and the introduction of a revamped “Ultimate Roguelike Morgue File,” so I thought I’d use this opportunity to share a rundown of how the old system worked before diving into the new one.

I must admit the old system was pretty dumb. I’m generally a proponent of brute forcing things using just what I know or can easily figure out and taking the simplest possible approach unless that really won’t work, and it happened to more or less work here, so I took the path of least resistance and it stuck that way for years.

Being terrible at web dev and and having trouble wrapping my head around networking and databases and whatnot, for this period I opted for a human-in-the-middle approach that can be summarized as:

  1. Copy scoresheets submitted by newly completed runs from the temporary directory to where they’ll be referenced by the leaderboards (the earliest versions of the leaderboards only offered name, location, and score, but this was expanded to link directly to the full scoresheet)
  2. Download all the new scoresheets to my local machine
  3. Run analyze_scores.exe to read in all the scoresheets and output the HTML data for the leaderboards
  4. Upload the latest leaderboards HTML file

I’m sure some combination of scripts could’ve handled this process automatically (and I did investigate and consider the possibilities), but I also justified keeping this workflow for years because having me in the middle for less than a minute each day at least allowed (forced) me to keep an eye on progress like how many runs were being completed each day, while also remaining vigilant in case something went wrong (this happened several times over the years as occasional bugs popped up, and I was usually able to respond before anyone else saw them because anything out of the ordinary had to make it past me first :P).

So literally once every day for 1,620 days I did the file copying and ran analyze_scores.exe on the data to produce the latest leaderboards. There were a couple days where I forgot or wasn’t able to update on time, but they were few and far in between.

I appreciate the many offers of help I got over the years (since a lot of experienced web devs happen to play Cogmind :P), but I wasn’t ready to take on help in this area when there was clearly lots more development to happen and changes to be made, and making these modifications alongside someone else would significantly slow the process.

I wanted to wait until closer to the end of beta when the scoring data was more stable and the format was finalized before doing something about it. “Doing something” being automating the process and making the whole architecture a lot more robust. For me, if long-term efficiency is the goal this sort of thing is best done once the specifications are clear and we’re past the point of making sweeping changes.

So Many Scoresheets

Over the years using this method we accumulated 89,904 scoresheets, a total 1.0 GB of raw text data.

cogmind_scoresheet_submissions_per_release_2015-2019_by_count

Cogmind scoresheet submissions per release, ordered by count (2015~2019).

Organizing the data chronologically probably tells a better story…

cogmind_scoresheet_submissions_per_release_2015-2019_chronological

Cogmind scoresheet submissions per release, ordered chronologically (2015~2019).

A number of factors affected uploads over the years and across different versions, contributing to a lower number of scoresheets than we’d have otherwise. Here are some of the more significant ones:

  • There were a few weeks in early alpha where I’d forgotten to actually save the scoresheets to my own records before wiping the server data for a new release, so while those leaderboards are still available, the underlying scoresheets have been lost to time.
  • We lost maybe a couple thousand runs shortly after the Steam launch in 2017 with Beta 3. Here I learned that storing this many individual text files on a web server is not something you’re really supposed to do, because they generally have a limit of 10k files in a single directory xD. I ended up having to quickly patch in support for multiple subdirectories, although this only affected by own analysis and data organization, not the game itself which could continue uploading to the same temp directory.
  • Score submissions were originally opt-in throughout Alpha, then later changed to opt-out when we joined Steam (to collect data from a wider group of players and get a clearer picture of the real audience), but then not long after we had to switch back to opt-in again starting with Beta 6 due to the new GDPR concerns.
  • The other reason behind the large drop heading into Beta 6 is that uploads were previously accepted from anonymous players, a practice that also ended with GDPR. Of all the scoresheets collected, 37,547 were anonymous (41.8%), though normally anonymous uploads accounted for two-thirds of the total within a given release, so if we were still accepting them now I’d expect to have about three times as many submissions from recent versions as we do now.
  • Earlier this year during Beta 8 the server went down for a couple weeks and wasn’t accepting score submissions, and even after being fixed there was still an issue that prevented some scores from uploading.

In terms of getting a clearer picture of the changes over time, even the chronological graph is somewhat distorted because it’s built on a per-release basis, even though some versions lasted only a month, while others persisted for several months or more. (Leaderboards are reset with each new release, rather than on a set time schedule.) Here I’ve compiled the monthwise scoresheet data, and annotated it with the above factors:

cogmind_scoresheet_submissions_2015-2019_monthly_annotated

Cogmind scoresheet submissions per month (2015~2019) (open for full size).

The compound effect of launching on Steam while also changing all new installs to upload stats by default really blew up the data, and it was interesting to collect and analyze some aggregate player metrics.

Of course the reverse happened half a year later upon switching back to opt-in and ignoring data from anonymous players. It’s true we could legally continue uploading anonymous data if it removes all unique identifiers completely, but to me data in that form is a lot less useful anyway, so for now I figure it’s best to just ignore it all rather than bloat the data set. Maybe later…

Uploading Files with SDL_net

On the technical side of things, how did these scoresheets actually get on the server in the first place? Again this was a big hurdle for me since for some reason this sort of stuff feels like black magic, but at least I found some resources online and was able to slowly patch together something that eventually worked.

Cogmind uses SDL (1.2.14), so when possible I tend to use SDL libraries for features I need. In this case SDL_net is the networking library so I added that in early 2015.

The solution for uploading a file ends up being pretty simple (once you have the answer xD):

sdl_net_http_upload_file

The basics for getting a file uploading via SLD_net. (You can download this as a text file here.)

That’s on the game side, but the server also needs a way to actually create the destination file itself and write the content to it, which is handled by a little PHP file targeted by the upload.

upload_file_creation_via_php

All that’s required on the server to accept the text data and use it to create a file locally (source, the extension of which would need to renamed).

And that’s it, just drop the PHP file in the target directory, and from the game call something like this:

Http connection(“www.gridsagegames.com”,”/cogmind/temp/scores/upload.php”);
bool uploadSuccessful = connection.upload(“filename_here.txt”,DATA_GOES_HERE);

This process ideally needs to be run in a separate thread to keep it from hanging the rest of the game in case the connection is slow or has other issues.

So there you have it, that’s how nearly 90,000 text files made their way to the server from 2015 to 2019!

analyze_scores.exe

The analyze_scores program is nothing to be proud of, that’s for sure. It was technically implemented within Cogmind itself as a special mode, making it easier to reference all the game data, structures, enums, and constants as necessary. Of course it wasn’t integrated throughout the game, just sitting there in its own file waiting to be ripped out one day.

By the time it was removed for Beta 9, it had bloated its way to over 6,000 lines, including lots of legacy code, unused features, bad organization, and confusing naming… basically a complete mess. I never bothered refactoring or designing it well in the first place because I knew it was going to be entirely replaced later on, though I guess at the time I didn’t realize “later on” would come over four years later, and in the meantime whenever I needed to go back and make modifications or additions it was a real headache to even make sense of whether I was doing it right or sometimes even what I was looking at.

Anyway, bad code. Good riddance.

By comparison the recently implemented replacement is both elegant and easy to follow, with all static data properly condensed into an external text file. I’ll be talking about that and more in a followup article (now available here).

Since I was stuck with it for a while but really didn’t want to actually rewrite the thing, an increasing number of features were just tacked on in place, some of the more notable ones including:

  • An external settings file. This was added so I didn’t have to actually recompile whenever I wanted to make minor changes to the program’s behavior.
  • A banned player list. I eventually needed a way to ban players by ID, so there was a text file against which the files were checked before analyzing any player data.
  • A file of alternate names used by a single player ID. I didn’t really need a file for this, but was mainly curious and added it anyway when finally implementing the ability for the leaderboards to detect and rank players by ID rather than name. The earliest iterations were based purely on names, which meant players could easily spoof other players, or individual players could easily occupy multiple places on the leaderboard by simply changing their name. Not very many players actually changed their name, and messing with the integrity of the leaderboards isn’t a big worry with a community like Cogmind’s, but it’s better to head off the inevitable.

A big part of the early bloat came from the  “Alpha Challenge 2015” tournament/event. For that I put together pretty broad-ranging “achievement” ranking system based on scoresheet data, so in addition to regular score-based leaderboards, players were also ranked in 66 different categories.

cogmind_alpha_challenge_2015_achievement_leaders_subset

Sample achievement results from AC2015. At first not all of the achievements were publicly defined beyond showing their name, so players weren’t entirely sure how or what they were calculating until the event’s conclusion, which helped prevent players from gaming the system which would’ve been easy (and therefore boring and unfair). Plus it was fun for players to guess what some of them might mean as they followed the event’s progress :D

I wasn’t sure whether I’d do another tournament like that, but I definitely wanted to, so the code stayed and it just sat there wasting time and space…

It was a lot of fun and I’d always looked forward to doing it again after first holding a similar mini-tournament in 2012 with Cogmind 7DRL, though in both cases we had a relatively small group of players so it was easier to manage, and “benefited from” no one being particular good at the game yet. Although I’d like to hold more competitions, there are two main roadblocks:

  1. A lot of the same people would win anyway--you can almost guess who they’ll be :P
  2. It’s not feasible to combat cheating, and there are enough players now that it would be a concern (especially if I offered real prizes, which I’d love to do as before).

There are some ways around these roadblocks, but various solutions comes with their own issues.

Anyway, back to analyze_scores.exe, it outputs a lot more than just leaderboards!

cogmind_analyze_scores_output_files

Files mostly created by analyze_scores.exe

Aside from the HTML file dropped onto the server, it also generates the materials to facilitate… you guessed it, analysis.

The userscores.csv file contains the complete contents of all submitted scoresheets for a given period, making it easy to examine the data in a single spreadsheet. There’s also the _best version containing each player’s best run so that each player only occupies one row, useful for different kinds of analysis.

cogmind_analyze_scores_output_userscores_best_sample

Sample _userscores_best.csv data.

Two other CSV files contain precalculated stats based on userscores data, organizing select factors that I wanted to keep an eye on across versions, and for easy conversion into graphs.

cogmind_analyze_scores_output_stats_best_sample

Sample _stats_best.csv data. “Best” stats are useful for exploring user preferences, since there’ll be only one set of data per player.

Although the userscores and stats analysis system were originally created for the tournament back during Alpha 3, it wasn’t until later starting with Alpha 8 that I decided to report on stats with each new major release. This is useful for keeping an eye on the general status of the community, and more specifically how recent changes have affected the game (and how players are interacting with them). Plus some players just enjoy having access to this information as well.

Stats have been published 14 separate times so far, all of them in this thread on the forums.

cogmind_alpha_beta_stats_history

History stats as linked from the leaderboards alongside the relevant period’s scoresheet archives.

I’m not sure how we’ll be doing stat analysis in future versions since analyze_scores is no longer a thing under the new system, but since from here on out we’ll have all the scoresheets collected in a brand new protobuf format rather than a trove of text files, it shouldn’t be too much trouble since presumably there is software out there we can use to run the analysis on that collection of data. More on that later!

Update 191226: The followup article covering the new system has been published, see “Leaderboards and Player Stats with Protobufs.”

Posted in Gamedev | Tagged , , , , | Leave a comment

Year 6 of the Cogmind

As we enter the seventh year of Cogmind development, time for another retrospective! Alongside several major releases and events, there’s also been a bit of a shift in development style as we edge our way into (very) late beta… Maybe to be followed by (very very) late beta and (very very very) late beta, but we’ll get to talking about that below :P

Here’s our annual image collage to start us off:

cogmind_development_year_6_small

Selection of images from the past year of Cogmind-related development as posted on this blog, forums, and Twitter (full mega size here).

Development Time

Got pretty close to hitting that 12,000-hour mark this year, but not quite (currently at 11,821 total hours)…

cogmind_monthly_development_hours_201307-201911

Cogmind Monthly Development Hours, 2013.7-2019.11 (click for crisper full-size version). (The color coding is for different aspects of development tackled each month, the subject of a future in-depth article to come at a later time.)

June and August were lower since I was gone for nearly four weeks of vacation during that period, but otherwise it was a pretty typical year overall. In the past 12 months I added another 1,676 hours, a slight 3.5% less than in 2018. However, work on the game itself totaled 852 hours, the first time that figure has outpaced hours spent on community-related work (purple) since prior to launching on Steam. You can see what I mean in the following graph.

cogmind_dev_hours_game_vs_community_2013-2019

Comparing the amount of hours directly spent working on Cogmind vs. community-related work (excludes hours that belong to neither category).

This year I mostly faded out the SITREPs which had been a staple of the announcements board since the Steam release, just occasionally writing one instead of consistently producing several per month. They were a lot of fun, but also a ton of work to put together, seemingly more than they were worth in terms of gains in other areas. Either way, I was kinda burnt out on them and wanted to shift more time back into development itself, and as you can see from the graph that trend has been successfully reversed. In 2017 and 2018, more time was spent on what I call “community efforts” than on coding and adding content to the game, but this year those efforts took a back seat as I was able to focus a greater portion of my time on the latter.

Despite the relative lack of SITREPs, I’ve definitely still been writing! There have been more blog posts this year than last (with even two more already complete ones I have yet to publish xD), funded in part by support on Patreon, which I’ll get to later.

Content that might have been given a cursory treatment in a SITREP is instead given a thorough design analysis on the blog, so we have things like the Ultimate Roguelike Morgue File series, a level design deep dive, several system-focused articles, and many others (the sidebar on the blog makes these easy to find chronologically).

I’ve also continued streaming, including earlier this year putting out a Cogmind: Pwning the Early Game video which has helped a lot of people improve their skills, so maybe check that out if you’re just starting out and having trouble reliably reaching the mid-game.

Aside from participating in all of this year’s timed special events, I also completed about five separate regular runs each using different tech or styles of play.

cogmind_stream_warlord_cheering

We’ve been having some good times :D

Back in March I took time out from development to help review 7DRLs, where I streamed most of the playing part of that process.

Releases

Naturally the highlight of making a game is to, you know, actually release playable stuff, so yeah we had plenty of that, too :)

cogmind_2019_releases_logo_collage

Cogmind release logos from year 6.

By necessity the frequency of major releases was lower this year, but the two big ones were really big:

  • Beta 8 “Forbidden Science” added the Exiles mini-faction, fleshing out the early game with some more lore and fun tech.
  • Beta 9 “Wizardy” was practically two major releases in one, with extensive RIF abilities to complement bothacking, a greatly expanded scoresheet system to power the upcoming new leaderboards and data analysis, and more.

Last year was the first time I’d ever held a timed event for Cogmind, just on a whim, but it was pretty neat so this year I’ve been doing more of that with bigger and crazier ideas. We’ve had another three of these since!

  • Limited Edition Holiday Mode: Last winter/New Year we celebrated with the only time so far that a decent chunk of Cogmind content has been released for a limited time, mainly due to the need to put together the special content for that build very quickly. So this one can’t be played anymore, but it was fun while it lasted, two weeks of different content each day. (Notice that Cogmind’s dev years are calculated starting from December, which is why this one’s included here.)
  • Pay2Buy: This real April Fools joke added ways to earn CogCoins and a persistent Cogshop to spend them in to buy items and loot boxes, with demand-based pricing and other new and modified mechanics.
  • Abominations!: Cogmind’s very own Halloween special, adding a scary new faction of 20 robots with a wide variety of new capabilities as well as a new hidden map complete with boss.

I wrote articles about designing these modes on the blog, and have no doubt there are more special events to come… no doubt at--oh my, look what month it is! ;)

In terms of regular features it’s been quite a full year, giving us the brand new but already much-loved “siege mode” mechanics for treads, an improved manual hacking autocompletion system (voted for by patrons), rebranded difficulty modes, colorblind accessibility options, Stream rich presence, and a ridiculous amount of QoL features. Basically way too much to list here, but that’s what changelogs are for, and you can of course check out all the relevant release notes for info and demos if you missed something.

cogmind_autocompletion_list_hacks

The new menu-based manual machine hack autocompletion system! It’s even more useful when searching for schematics.

Community Support

How are we still going strong after all these years? You! I very much appreciate the support Cogmind and my work have continued to receive, both financial and otherwise.

Part way through the year I decided it was time to start a Patreon (announcement) to help see us through the end of Cogmind beta and also gradually build it up so that by the time Cogmind is finished we have a decent supplementary source of revenue to continue enabling a broader range of work rather than focusing so much on commercial concerns.

Sure there are the transactional perks like guiding some aspects of development through voting and trying out new features in advance, but I prefer to emphasize that it’s more about taking some of the risk out of an inherently risky business to keep it both creative and sustainable.

Anyway, in last year’s annual review I mentioned I was getting worried about revenue, but that has successfully stabilized for now, in part thanks to Patreon which has made up for inevitable lower sales over time. “If only” I was one of those devs who more quickly releases smaller/medium-sized games (even sticking to projects that take a year or two would mean I’d have several out there already!), but I’ve always been more of a big project kinda guy :P. Besides, there’s something to be said for megaprojects as long as there’s a strong community behind it and the design fundamentals are solid!

Outside Patreon, special thanks to PlasticHeart, Reed, Matthew, Karl and others for your donations this year <3

Overwhelmingly Positive

Just a few weeks ago after more than two years of collecting reviews on Steam, Cogmind finally got enough of them at the right ratio to achieve the relatively rare Overwhelmingly Positive rating!

cogmind_steam_reviews_500_191114_overwhelmingly_positive

November 14, 2019, was a nice day :)

As promised, this means I’ll be adding a significant expansion around a new “Merchants Guild” faction. And now that we’ve hit that goal, I’ve already set the next one: if we can maintain this rating up to 800 reviews, I’ll also add a new derelict community.

The concepts for both expansions are fairly fleshed out at this point, but building either/both will take quite some time. I’ve also even added a third but smaller expansion as a Patreon goal… As I see it there’s still room yet to liven up the world even more!

IndieDB

It’s that time of year again when IndieDB does its Game of the Year voting, and although Cogmind is too niche to pull the numbers necessary to compete against mainstream titles for the Top 10 in the second round, for the past five years in a row we’ve made it through the first round of voting to take a spot in the Top 100 list.

So if you have a chance give us a vote there, along with all your other favorite roguelikes/indie games that have a page on the site. (Registration isn’t required, but if you do have an IndieDB account you can maybe win a key for one of many different games if interested.)

2020

With the year coming to an end, what’s in store for 2020…

Well, I originally said I’d like to reach that stage in 2019, and apparently it didn’t happen! We are really close, and it’s been within reach for literally all of the past year, but I’ve been working on fine-tuning or expanding other parts of the game anyway and in hindsight I think that’s been a good use of time given the results. This is especially true with regard to improving the early game and new-player experience by rebranding the difficulty modes and adding the Exiles/FarCom, plus many other related features--going 1.0 brings in new players, and naturally we’d hope that experience would be a good one.

cogmind_dev_roadmap_191112

The current state of the development roadmap from the FAQ (snapshot from 191112).

As per the roadmap, the only remaining feature absolutely necessary before 1.0 is to finish the audio, which should be a focus for the upcoming Beta 10 depending on how much else I have to get done before then. In other words, it’s getting harder for me to postpone 1.0 :P

That said, 2019 ain’t over yet, we’ve still got a few weeks here to do something with…

Posted in Annual Review | Tagged , , , | 2 Responses

Movement QoL

Movement is pretty convenient in Cogmind, an important goal to aim for since this is generally the most common action players will perform in a roguelike.

Four different methods are supported out of the box, the mouse along with three different keyboard-based methods (numpad, arrow keys, and hjkl).

cogmind_player_movement_input_preferences_beta8

Movement input preference distribution (*only includes those opting in to reporting).

About the same number of players use the mouse and numpad, with the remaining minority divided between arrows and hjkl.

Moving via mouse cursor is certainly easy to use--simply click somewhere to go there automatically, although the keyboard offers finer control so it’s no surprise that in a tactical roguelike like Cogmind technically a majority of players end up relying on the keyboard for more or even all of their movement.

About one-fifth (21.3%) of players (including myself) even exclusively use the keyboard for everything, so for a while I’ve been mulling potential improvements and additional features for keyboard-based movement. According to the stats, newer players are more likely to start with mouse movement (unless already familiar with traditional roguelikes), but either way the chance of someone becoming a keyboard player goes up over time as they play more and learn how to do some actions faster that way, might as well continue improving the experience!

Keyboard Running

Although players new to the genre tend to assume “running” means a mechanics-relevant action where your character is actually “moving faster,” in terms of roguelike input schemes it actually refers to moving multiple spaces in a (usually straight) line via a single command.

Cogmind has had running ever since the original 7DRL release, because it really saves a lot of time (also more specifically back then mouse-based movement wasn’t a thing, so there needed to be some convenient way to quickly cover long distances!). At that point it was the dumbest implementation possible, though: “automatically move in one direction until something blocks the path.” (okay it could’ve been dumber--it also immediately stops on spotting an enemy, but I’m only talking about layout detection for now)

Smarter approaches need to take more of the layout into account, stopping not only at obstacles, but also where the player might want to reconsider their direction, or perhaps take another action.

That’s what I did for the alpha release, adding a new rule that the player should automatically stop running on reaching a location where the current cells immediately to the left and right are either doors or open space, and one or both of those adjacent to the previous position were not also open. This properly stops when arriving at doors/doorways, or arriving at a side-corridor while following a wall. It doesn’t stop, however, when running straight into a narrow corridor, so that’s a good thing.

cogmind_keyboard_running_diagram_old

Sample stop points from the original running system, demonstrating the direction from which the run is coming from, and the two points which are compared to trigger the stop.

At the time I left a note in my code that this could be better, but would get complicated fast and I wasn’t sure what use cases we might need to address before it was ideal, so that’s as far as I went.

Honestly if your running system gets too complicated it’ll be both annoying and unpredictable. Predictability is important, since mistakes can get you killed and no one’s going to be happy using a UI feature prone to “error” (where UI errors are defined by players as anything they did not expect/want to happen). Basically the system should stop as little as possible while still being as useful as possible.

There was definitely still room for important improvements, so that’s one of the areas I was working on recently.

For one, the original system was only applied when traveling in cardinal directions. While this is sufficient for most use cases since corridors only travel in cardinal directions anyway, sometimes diagonal running through an open area might hit a layout where it should stop. Rulewise it’s not really much different from cardinal movement, just looking at different cells.

cogmind_keyboard_running_diagram_diagonal

Sample stop points from the new diagonal running system, demonstrating the direction from which the run is coming from, and the two points which are compared to trigger the stop (or in the special case of the door, that one is simply checked for explicitly).

Another improvement to address is the fact that the first diagram up there for cardinal running ends up being suboptimal! Although on reaching a corner you may want to move out beyond it in order to look around, there’s also the chance you intend to simply round the corner without looking first, in which case you can move one less space by taking the diagonal, an option which is lost if the running behavior always moves out beyond the end of the corner as it was before. Same goes for arriving at doors--open them by standing out in front, or by stepping into the doorway? The choice should be yours.

cogmind_keyboard_running_diagram

Sample stop points from the new running system, which is like before only it stops one space sooner because it’s looking for the same triggers out ahead.

Here’s a sample of moving around using the latest iteration of the run command:

cogmind_autorunning_behavior_update

Automatic stops managed by the run command.

Other possible steps include automatically turning corners if moving down a narrow corridor that changes directions, although I think this is more suitable for other roguelikes (some have this feature), and not as common or useful in Cogmind. Of course QoL features always need to take a game’s unique characteristics into consideration rather than blindly implementing them.

A more reasonable possibility is to mimic mouse-driven pathfinding’s ability to optionally circumvent temporary obstacles like other robots. This is something that could theoretically be added for keyboard running as long as given the circumstances it won’t change the distance of the path, although I’m not sure I want to go there because it could in some cases have negative consequences and maintaining predictability is important here. Not to mention keyboards are all about that fine control, so running to an obstacle then quickly choosing how you want to deal with it--maybe wait, or choose a direction to go around, is probably preferable.

Keyboard Pathfinding

One of the main features I wanted to tackle with this round of movement updates was keyboard-based pathfinding. Although a lot of keyboard users don’t mind cellwise movement combined with a bit of running here and there, I always thought it would be nice if possible to choose a destination in keyboard mode and use a single command to move there via the quickest route, just like the mouse can by simply clicking on a location, regardless of how far away it is. So I’ve finally added it!

Using the mouse still has the advantage here and there were certainly some UX problems to work through, for example how to continue traveling to the destination after being interrupted--it would’ve been annoying to have to use examine mode to find the destination again!

To use this new feature, players have to be in examine mode (where you move the keyboard cursor around to inspect things) and simply press Shift-Alt-g (for “go”) on the destination. The result is just like a left-click on that location.

cogmind_keyboard_pathfinding

Keyboard pathfinding demo (like using the mouse, holding Ctrl-Alt highlights the path).

Technically this exits examine mode for the move, but key to the UX is that the destination is remembered, so until another destination is set, any time Shift-Alt-g is pressed Cogmind will resume approaching the destination. This is essential for when passing by a move-interrupting hostile like a Watcher.

cogmind_keyboard_pathfinding_continue

Keyboard pathfinding demo w/resuming.

Because it uses examine mode, keyboard pathfinding can also be useful when looking at items on the ground, e.g. while moving the cursor around to inspect salvage, if you find something you want a quick Shift-Alt-g brings you right to it.

For both this and mouse movement I’ve also finally made it so that setting an interactive machine as the destination automatically starts hacking it on arrival. After all, anyone who doesn’t want this effect can choose an adjacent cell as their target instead, and most anyone who is clicking on the machine’s terminal itself quite probably wants to hack it.

cogmind_pathfinding_start_hack

Pathfinding to a machine automatically starting the hack on arrival.

Adjustable Movement Delay

Another optional feature I’ve wanted to add for a while, one I’m quite curious if anyone will actually use, is to make the movement delay adjustable. “Movement delay” determines how long to wait before taking each move while traveling along a predetermined path, be it keyboard running or mouse/keyboard pathfinding.

Cogmind’s default has always been 100ms, a balance between “getting there quickly enough” while still being just slow enough react to unexpected changes. Quite a few potentially dangerous (or at least important to stop and observe) situations in Cogmind will stop automated movement anyway, but moving too quickly is still somewhat dangerous for the same reasons autoexplore is a bad idea in Cogmind. Among them are situations that can’t feasibly stop movement automatically, like threats on sensors changing directions, not to mention the high likelihood of wasting turns, which is generally bad in Cogmind since the passage of global time matters a fair bit.

cogmind_movementDelayInterval_demo

Demo of fast movement with movementDelayInterval=10.

 

cogmind_movementDelayInterval_demo_keyboard

Running delayed by only 10ms is pretty crazy! (recorded before the addition of rules to autostop diagonal movement near some layouts)

As part of this update I also fixed the issue of the unreliability of stopping running/pathfinding in progress, even more important now that the speed is adjustable because the problem could have become more pronounced with lower movement delays. (Normally any input made while moving should immediately stop the move, but input is also generally blocked while actions are being carried out, and the way it was blocked happened to also sometimes be ignoring even input that should technically be doing the stopping xD)

Still, I don’t really recommend changing this value from the default, I mainly just added it because it was easy to do and options are good, right? :P

Move Blocking

Stopping movement in progress, or even preemptively blocking a move before it begins, is an important part of roguelike QoL. (Technically this principle also applies outside movement as well, extending to other questionable or potentially dangerous actions, but this article is about movement in particular.)

I’ve heard players of some roguelikes (though not Cogmind, to my knowledge) argue that games shouldn’t protect players from their own stupid mistakes, but I think of it as basic QoL because if anything this sort of protection allows for more efficient play without having to waste time thinking about avoiding stupid mistakes, instead focusing on in-game tactical decisions rather than fighting the UI itself.

Anyway, over the years I’ve been adding quite a few of these, and will keep adding more as necessary. Below are some of the movement-related blocks I’ve implemented (none of these are new)

Non-optional blocks:

  • New combat threats entering FOV
  • Ramming (this was one of the first new blocks added in early alpha, and I can remember how chaotic it was back then with players constantly ramming into non-combat bots while moving around)
  • Spotting a new hostile trap
  • Moving onto a known trap
  • Flying while overweight
  • Sudden system corruption effects like heat surge (sometimes responding with an immediate increase in dissipation might be necessary) and part rejection (don’t want to leave some random part behind!)

Optional blocks (all active by default):

  • New non-combat enemies in FOV
  • Exiting to another map
  • Melee attacking a neutral target by moving into them
  • Melee attacking while flying (since the intent may be to simply jump)
  • Speed is very slow (>= 3 turns per cell)

The way temporary blocks generally work is they block the move and continue blocking movement for the next 500ms (while showing a warning message). This is important because movement is often a repeated action and players need time to respond to whatever it is they need to be noticing here, rather than just repeatedly mashing keys. Then once past initial warning period, there’s a window of about 2500ms during which that same warning won’t be shown again and the player is free to do that action (or maybe change their mind and do something else).

The one exception to the block window is spotting threats/enemies/traps, because those happen frequently and the main purpose there is to just block running/pathfinding rather than additional individual moves, so spotting a new enemy continues allowing movement freely.

What Else?

I’m sure more things will eventually pop up!

One internal movement-related feature that needs work is the pathfinding heuristic. The current pathfinding is actually pretty wasteful, and is one of the main processing bottlenecks for the game, but I’ve already done a few rounds of optimization as necessary and it’s fast enough, so that’s still been sitting by the wayside for if/when we need more performance improvements.

In its current state pathfinding (for all the AIs, mind you--just pathfinding for the player is negligible) doesn’t cause problems under normal circumstances, though the heuristic is clearly off when looking at how it searches for a path, checking more cells than should be necessary:

cogmind_pathfinding_visualization_trace

Pathfinding heuristic at work :/

Anyway, I tried before and couldn’t immediately find any way to effectively improve it (the obvious solutions weren’t working for some reason xD), but there are also other ways to optimize pathfinding besides the heuristic ;)

Posted in Design | Tagged , | 2 Responses

Steam Rich Presence

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.

cogmind_steam_rich_presence_first_test_status_friends_list

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:

cogmind_steam_rich_presence_first_test_status_detail

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:

cogmind_steam_rich_presence_first_test_status_detail_death

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.

cogmind_steam_rich_presence_first_test

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

steam_rich_presence_localization

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…

cogmind_steam_rich_presence_source_h

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.

cogmind_steam_rich_presence_source_cpp

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:

cogmind_steam_rich_presence_first_test_screenshot

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!

Posted in Gamedev | Tagged , , , , | 2 Responses