2020/08/16 - games
Quake 3 demos are a cool kind of file that records game events to be re-rendered and replayed automatically. It reminds me of this piano my aunt had growing up that had scrolls and used compressed air and could automatically play itself. In a game development class in college, we learned how “Arcade mode” shows game play to attract players over to the game. This is my intention for demos with Quake 3.
Quake 3 plays demos by recording every network message the server sends. Then it plays back the network message for replaying the demo. The network messages update the players state, and the engine renders the scene as if you were currently in a match playing the game. It’s kind of like an adjustable movie in the sense that the engine is re-rendering the actual game state, as opposed to taking a picture and making a demo movie. Taking a movie capture of the game is also an option with the Quake 3 engine, but not discussed here.
Demos are recorded client side by default. But recently, I heard about this idea called “server side demos.” I’ve never seen this in action or being used, but there is a lot of work on it documented here. When I went to implement, I found this incredible thread. The guy explains step by step which and how he implemented server side demos. Basically, there have been many attempts at getting this to work. Each method has some benefits, so I took all 3 types of demos and combined them into one. Let’s review.
I first heard about this from the guys over at Tremulous. They had a setup where players can join the server, then watch a recorded demo play out as if it was a live match. You can even spectate, fly around, and follow the players in the match. This is neat because it creates a shared experience. Every player connected gets to see the same thing happening in the game. They can even vote to play a specific demo recording.
This seems so obvious, that’s part of what makes it cool. Since a client can record network events and make a demo, this patch created by “TheDoctor” records every single client to a separate file from the server's end. So instead of recording incoming network events from the server, the server goes through each client and records the outgoing events to separate client dm_68 files. This is cool because it might allow me to transfer a demo to a client, or replay a specific sequence as the action continues for the other players.
This is probably the most bizarre kind of demo I’ve heard of. Cyrax from Q3e moved some of the demo code from Excessive+ and CPMA into an engine level solution that is not mod specific. This is a truly amazing piece of code. A few files edited server side, and I can join the multiview protocol and receive a copy of every client's network events as if I was playing as every client. It packs all the network events from every client connected to the server into the data that I receive. I can switch views and see the game as if it is being played from their own computer screen. I love this implementation because it feels like it is really close to supporting instant replays, body cams on teammates, split screen, showing an overhead view of the map. Very cool game dynamics possibilities, ALL engine side!
What does engine-side mean? It means I didn’t have to edit the game code, which is already compiled, to get it to work. This is a good thing because some “Game Codes” come from other people. I don’t have control over other people’s code, but they can still use these features if they load up their game using my engine. All of these demo modifications are engine-side, so they work on many different games and mods that play on this engine.
My next goal is to fully support split screen. Using the engine to spy on other players. Hopefully, I will be able to add instant replays and switch back and forth from playing the game to watching the game instantly. I also thought it would be cool to load another mod and when you look in the mirror it cartoonifies your character.
My original goal was turning quake 3 into an open world battle royale. And eventually, I’d like to even combine different game plays, like one team could be capturing the flag, and one team could be in a deathmatch and they can all interact and fight for their goals. Playing demos also works now in the browser build.
I found a few bugs in my lazy loading modification. It came down to another piece of the file-system and not downloading/reactivating the shaders after the level loaded.
fs_game was getting reset, since it matches the default
fs_basegame. Another reason this entire engine needs a good suite of unit tests.
I am focused on adding some file format support. Currently a 30 second movie uses about 30 MBs for in-game videos, but if I re-encode it to .webm/VP9, the same video is only about 1MB in size. There may also be some quality and size improvements using webp images.
Now, by default, the player is dropped into a running game. The client is connected to a local dedicated server running in a web-worker. I was able to reduce the load on the main thread by about 15%, leaving more CPU for frame-rate, which I can now get up to about 150 FPS on a small map, and around 90 FPS on a larger map like q3dm16.
The local server also shows up in the list of Local servers, so the user can connect back to it. The local server game is also “broken out” of any firewall or LAN network using the SOCKS proxy. Anyone can connect back to your local dedicated server. This has come with a number of single player issues. Using a dedicated server for local games is completely different from the original design, but I saw that most of the communication happens over the Net interface. Because local games use a loopback indication over the net interface, I was able to reconnect as if the built in server was being used on the same thread. I connected the loopback interface to postMessage() in the worker. Every time a loopback packet is sent, it is transmitted over the web-worker postMessage() interface, and then read into the loopback queue in the dedicated server worker. Server commands are handled using the existing command interface, but instead of requiring
\rcon at the beginning of the command, it processes all the server commands as if it was connected locally.
Doing a little extra cleanup. I’ve turned the local servers list into a cvar by using cl_master1 through 24 (as opposed to sv_master1 through 24). I added a cvar for some extra rcon passwords. I also fixed a bug where the map_restart command would cause a crash that turned out to be related to the bot AI not restarting properly because the single player is disconnected from the dedicated local server. The script actually adds the bots at the start of the match, and the entire game VM is reloaded in memory.