Counter-Strike ∙ Programming ∙ Demos ∙ Go ∙ PHP ∙ Laravel ∙ Stratbox

How I created the ultimate Counter-Strike nade lineup database (Part 1 - The Backend)

Like seriously, nothing else is going to come close

The title may seem like hyperbole, but I assure you it isn't.

Over the past couple of months I've been working on the ultimate Counter-Strike nade lineup database. And the best part? It's entirely automatic. In this post I'm going to walk you through exactly how I made it.

Prefer to watch? Here's a video instead!

The Game

Automation

First step is to figure out how to automate CS2. In Global Offensive, this was fairly straightforward. You use the -netconport launch option, fire up a telnet connection and start issuing commands. Telnet is essentially a console connection over a network, if you want any control over the game from your code, Telnet is what you need. Counter-Strike has a great developer console, basically the entire game is controllable via the console.

CS2 launched with a broken telnet implementation on Windows, and I kinda need Windows since that's what HLAE (more about that in a second) is built for. There's a workaround with using the workshop tools, or you can use a patched DLL to initialize the required winsock library (thanks runie!). I did write an email to Valve about this, but we're still waiting for an official fix.

With a telnet connection, you can write some code that listens to the in-game console, figures out what state the game is in (menu, loading, in-game, etc) and create a bot to record the game. Perfect.

I've majorly condensed this down. The real process took me a number of weeks to refine, going through several iterations. I started in NodeJS before migrating to PHP with the React library for async processing, as Stratbox is built in Laravel, I wanted to keep it all in-PHP if I could.

Recording

Now, onto recording. The easiest way to do this is using Half-Life Advanced Effects (or HLAE for short). HLAE is the tool used by all movie makers and observers looking for those nice smooth camera effects and high quality recordings locked at full framerate.

Compared to the version for Global Offensive, HLAE for Source 2 is a lot more basic. However, it still has the recording features necessary.

With HLAE, you can specify commands to be run on a given tick in a demo, so you can specify exactly which tick you want your recordings to start and end. Very handy.

The Demos

Getting the demos isn't an easy task to automate. You have to get them from HLTV, who don't like tools downloading their demos. That said, I managed it, I then store a copy on my own infrastructure so I don't have to repeatedly download from them. I originally was using a Backblaze B2 bucket, but I transitioned to using a MinIO instance in my loft with 12TB of hard disk space to save on bandwidth costs.

Next step is parsing. I use the fantastic demoinfocs-golang library for this. I parse the demo and get every single nade thrown in the demo and store it in my database. This table is going to be huge, so I've made sure to only store the values I absolutely need.

  • Where the player is aiming
  • Where they're standing
  • Where the nade lands
  • What type of nade it is
  • Standing or crouching?
  • Middle, left click or right click?
  • What map is it?

Storing the nades is done by the main Laravel application, the go script simply provides it a JSON dump to read from. Before inserting I query the nade table to find nades with similar values, so I can group them together. There's no point me recording videos of 200 instances of the same grenade throw, so I pair them up together.

If similar nades are found, the first instance of the nade is declared as the parent. So any following nades will be given a parent_id, pointing towards the original version. I've created an index that covers all the columns queried to increase the speed of this query.

Once that's done, they're ready for rendering. Any nade that has more than 10* children is queued up for rendering.

  • this number has changed and probably will change. (It has, it's now 20)

Recording

I can use my game automation above to record videos of the nade throws, once that's done I slap a watermark on them with FFMpeg, grab a thumbnail (the mid-point of the video) and upload it to cloud storage. From there, I can serve the nades up.

Next Steps

Next is making it usable! I'll get a post written about that some time soon™️, in the meantime if you want a deeper dive on any of these elements, reach out on Twitter

Let's Chat

email: harry [at] hjb [dot] dev
discord: indexgg