The code is clear, coherent, and straightforward. I’m not going to say it’s the best source code I’ve ever read, but it set a high bar. I’ve read source code to games since then, and I’ve seen all sorts of weird stuff… I’ve seen functions with nesting levels that go past the right side of the screen, I’ve seen functions a mile long that do a million things, you know. It was an early lesson that you could do cool things with simple code.
We used Quake and Quake II to teach a VR class to kids in the late 1990s and early 2000s. You got a VR headset, you got a half-dozen PCs with Q.E.D. or Qoole, and you taught a room of 12-14 or 14-18 year-old students how to make their own virtual reality game.
“Make your own virtual reality game” was, essentially, making your own Quake or Quake II level, without any monsters or guns in it. The story for the game could be told through on-screen messages triggered in the level. Students made all sorts of levels. One group made a level with slime you had to cross, and a button that turned on pumps to drain the slime (very much like what you would see in a standard Quake II level). Another group made a 3D maze out of water that you had to swim through, floating in space. I still have some of these student-made levels on my hard drive, after all these years, although I have trouble getting some of them to work.
For the class, I made a mod that gave the players a flaregun instead of the blaster. Basically, it was still the blaster, but with a different sound effect and no weapon model. I modified the blaster bolt to be affected by gravity, bounce off of things, and go out after a certain amount of time.
(If you were in that class—you may not have gotten the headset. We couldn’t always get it to work.)
Certainly id's code is clean and well-structured, however I always had a tough time trying to grok it as a layperson. Comments (those within the functions) are relatively sparse, and the code often contains quite cryptic expressions that are somewhat bewildering to those without an high understanding of 3D graphics.
This, of course, makes perfect sense as an output from an industry leader in the field (Carmack). However it definitely requires a lot of foreknowledge and, dare I say it, a mathematical bent to follow with confidence.
"I’ve read source code to games since then, and I’ve seen all sorts of weird stuff… I’ve seen functions with nesting levels that go past the right side of the screen, I’ve seen functions a mile long that do a million things, you know. It was an early lesson that you could do cool things with simple code."
Guilty as charged!
I used to chronically write macaroni code. So you would have like 25 lines of code that ended up being the back bone of like a dozen things that would have another dozen things stacked on top of each. Would execute super fast BUT would amount technical debt quickly and eventually said functions would become untouchable because you would risk breaking a lot of stuff build on top of stuff.
Comments would try to clear it up, but communication skills where not the best. Explain function but not workings. Things like - "Does SINE table". "Table Defrag". "Binary resolve" etc.
Get a lot of people going "What the F*K is this shit?! But... it does the job damn fast". If it was any field other that video games, I would not have lasted long and neither would the product...
I never scrutinized the id software source, but man have I seen some horrifying game source. The bar is extremely low.
What's surprised me is that successful, fun to play, and stable enough games have been shipped with such absolute unmitigated disasters behind the curtain.
It makes me question sometimes all the effort(and time) I put into preventing the chaos, when such carelessly bodged together garbage can be perfectly profitable.
That's usually because the high level gameplay code needs to be incrementally tweaked over months or years based on very subjective feedback like "this doesn't feel quite right, can you maybe try...".
You can start with a great plan, but no plan survives gameplay- and balance-tweaking for very long.
Ideally the lower layers are much more structured though, usually those need to be maintained over a longer time and across games.
That garbage code brought about the speed run culture, highlighted by charity events like Awesome Games Done Quick. The speed runner and their crew on stage will explain the latest level skips, wall clipping to avoid tedious areas, enemy weaknesses and exploits, and frame perfect input strings needed to accomplish those. And some will perform speed runs blindfolded with only the audio cues to work with.
The id software source code gets increasingly cleaner for each game I think. I've read the Doom source code and Quake II's. I remember Quake II being very clean and elegant. Doom is not quite there. It's fairly well organized, the functions are generally short and concise, but it's a mess of global variables.
Whoa, I still remember watching a show about VR on the Discovery Channel around 1999 when I was a kid. They used Quake 2 as a demo, and it blew my mind. I was playing Quake 2 on my PC also, but the concept of VR was revolutionary to me at the time. It's stuck in my head to this day. To now learn that using Quake 2 was a common practice for teaching VR. Maybe it was you in that documentary!
Talking about clear and coherent, does anyone know if there is a deeper reason for using `break' and `continue' instead of pure structured programming? The snippet
i = ent->client->chase_target - g_edicts;
do {
i++;
if (i > maxclients->value)
i = 1;
e = g_edicts + i;
if (!e->inuse)
continue;
if (!e->client->resp.spectator)
break;
} while (e != ent->client->chase_target);
for instance, from the function ChaseNext in the file original/rogue/g_chase.c can be reduced to
i = ent->client->chase_target - g_edicts;
do {
i = i % maxclients->value + 1;
e = g_edicts + i;
} while ((! e->inuse || e->client->resp.spectator) && (e != ent->client->chase_target));
which in my opinion is clearer since the exit condition for the loop is in one place.
I find the latter - with all the conditions much less clear. I have to pause and reason about each condition and it’s relation to the others, the order of the comparisons and the effect of short circuits.
The first example makes it very clear that condition A means do it again and condition B means exit this loop while condition C establishes the boundaries of the loop.
I think it's debateable which is clearer. Your while condition requires some mental parsing of the booleans whereas the original can be analysed one at a time.
Using % vs comparison and reset was probably more efficient with the compilers of the time; might still be.
Also I doubt everyone would agree that the one exit condition is more clear.
For example, if I know that if e->inuse is 0 it will continue with the next round. Arguably it's more difficult to understand that from the single combined expression—because it isn't so: if !e->inuse but e == ent->client->chase_target, the original will loop but yours will exit.
Though I'm guessing ent->client->chase_target->inuse is probably never 0.
Break, continue, return (and arguably even C's goto before variables could be declared in the middle of a scope block) are all entirely normal tools in the structured programming toolbox. How those are used is just personal taste.
-----------------------------------------
John Carmack's .plan for Jan 31, 1997
-----------------------------------------
I went down to the ferrari dealership today with Ann and American with the intention of buying a new f355 as a "sensible" car (in contrast to my other twin turbo monstrosities).
There was an F40 sitting out front.
Umm. Sensible car. F40. Sensible car. F40. Hmmmm.
American played the evil conscience and Ann played the good conscience. For a while. Then Ann got infected by the Dark Side. :-) I bought the F40.
Ok, I now have too many ferraris. I have a plan to fix that. This hasn't gone through all the legal crap necessary yet, but it is my intention to give away my first ferrari as the grand prize of a Quake tournement.
Yes, I am serious.
DO NOT SEND ME ANY MAIL ABOUT THIS! When we have more to say about it, we will make a formal announcement. This would be a good time to start a heated debate in the newsgroups about what the fairest tourney rules would be, though.
The specs:
1987 328 GTS with turbocharged engine.
Feature in the january, 1994 issue of Turbo magazine.
Made 360 HP at the rear wheels on a chassis dyno.
New engine (I melted the first one...), new paint.
The interior could use a little work.
I plan to also include enough cash to cover tax and insurance.
I'll drop my Quake II anecdote here. Quake II was the first FPS I ever beat (at least I think I beat it). I did it over a week in the summer when visiting a friend out of state (never would have been allowed otherwise).
When I started, I had only ever played the original DOOM, so I used those controls, and lo and behold they worked, and I thought nothing of it. At some point as I was playing along, my view suddenly became locked to the ceiling and I was thoroughly confused and stuck. Turns out I had bumped the mouse, and managed to figure out that mouse look was a thing after extended fiddling with the system to try to determine the cause and get back to killing badguys. Needless to say, the game got quite a bit easier after that.
There wasn't internet, neither I nor my friend knew what mouse-look was, we had never seen anyone play a true 3d fps. It is hard to imagine how obscure and alien the interfaces for games are because of how pervasive the knowledge has become.
In case you were unaware, this is actually the source code of the rerelease of Quake II. The source code for the original Quake II has been released for many years[0], along with many of the id Software classics[1].
In case you were unaware, the source code of the original is included along with the re-release in this repository, as stated in the first sentence of the readme.
You reminded me of the first time I saw a lan full of people playing an fps.
I was at a ComicCon in Rome, Italy, circa 1999, anyway Half Life just hit gold production and released multiplayer demos there were at least 20 people all playing on lan multiplayer half life, with mouses too! Crazy.
I went to many many lan parties since then up to 2005, then online match making killed quakenet and socializing.
Tribes 2 was my first taste of modding, the game holds a super special place in my heart too. Then came Ultima Online emulators Sphere and RunUO that turned me from a gamer into a programmer too.
I wish I could find a use for that, it is a funny idea. Unfortunately in a world where all of you stuff is in terminals, having one special drop down one is less useful.
Incredible and just sent me way back to the nineties. To be fair, I'm talking about the first Quake game here. I had a PC rig on my bedroom floor which as I recall was a Pentium 166MMX, and my friend came over with a similar machine which was a bit faster (a 233) perhaps. No LAN cards insight.
Using one of my dad's ancient parallel cables and fooling Windows into thinking it was a LAN connection, which worked well enough, we played for days, until it got kind of tedious. So we then got into the whole modding scene - not creating anything, but downloading and testing the things which were out there. I will never, ever forget Girobot and KQP (Killer Quake Patch).
The Girobot bots were a force to be reckoned with when they didn't get stuck in the terrain. It was KQP which added all kinds of crazy mods/weapons/bot systems to the game that was the real gold. The Vampire Gun left us in stitches every time someone was hit by it, because it basically guaranteed a slow but not too slow death in which the hit player would crumble into little chunks of flesh. I miss the nineties very, very much.
First few computers were too slow to run Quake effectively, but then I had a Duron 900 (they ran slow AND hot!), bought a 'PC Powerplay' magazine that came with a CD full of quake mods, and a map with 20-40 of ZeusBots worked just fine!
The endless amounts of user created mods, maps, and skins many of us enjoyed in early days were killed off so that game publishers could make a fortune charging us for a small selection of sanctioned DLC. In the process they killed off an entire ecosystem that grew up around user created content. Forums, review sites, file hosting, and generations of new talent who found their love of programing by creating mods all lost due to greed and a desire for control.
Indeed. My brother and I played starcraft using the same trick. When the fight started to escalate and there were hundreds of units fighting, the lag got really really bad, but it worked well enough that we kept coming back to it. That and Warcraft 2, Heretic, Hexen, all the rest. The best days in gaming IMO.
Quake 1 (original DOS version only) supported null modem, but Quake 2 didn't -you had to use a null modem with Windows 95 dial-up networking, which was a bit more tricky to setup.
I saw grumbling elsewhere about how the re-release doesn't include a Linux version, but here they are with the source and it claims to be tested with clang. Big props to iD for sticking to their principles.
I wonder why they released the source code for this one. Is it purely because of the principles, or did they have a legal requirement since the old one is also GPL licensed?
I'm assuming they don't have legal requirements since they owned the old code anyway.
John Carmack has been fairly vocal about releasing the source code for his games, particularly the Quake/Doom series for as long as I can remember, going back to at least Quakecon 2004 during one of his keynotes there. I think at the keynote when they announced Doom 3, he even talked at length about eventually releasing that engine as open source, I think in 7 years (which eventually did happen). He is not the flag carrier for open source games, but he may be one of the most consistent at following through on open sourcing their base engine over the last 20 years. Before leaving Meta as CTO he was speaking about making sure the GO and Quest 1 were usable in offline mode with sideload capability when they become EOL'd. In my view this release of code is not at all surprising in the least.
ID has traditionally release code for their games when they reach...I forget, 10 years, or end of official support or something. This is just continuing the tradition of giving their games the longest of tale by opening them up. Check it out. https://github.com/id-Software
I remember in one if Carmack's last Quakecon keynotes, after the acquisition, he was talking about future source code releases. He didn't say anything definitive, but he did tell a story where he spoke with a Zenimax lawyer, and they told him how important the GPL releases were.
This is not the engine code, this is the game code that was released even prior to Quake 2 being GPLd.
-----------------------------------------
John Carmack's .plan for Dec 11, 1997
-----------------------------------------
The Quake 2 public code release is up at:
ftp://ftp.idsoftware.com/idstuff/quake2/source/q2source_12_11.zip
This source code distribution is only for hard-core people that are
going to spend a lot of time pouring over it. This is NOT a
how-to-make-levels-for-q2 type dsitribution!
This should keep a bunch of you busy for a while. :)
They shipped this version on all of the major platforms, and I'm reasonably sure that Nintendo and Sony both use Clang/LLVM for their SDK toolchains nowadays. Maybe Linux was on their mind but getting it to build for modern consoles will have been the main motivation.
There are so many games that aren't being sold anymore and aren't playable on modern software/hardware stack.
I wish there were a law that if you don't release a patch for your software for 20 years to make it workable for newer platforms, you've to release it as open source from the perspective of cultural significance.
What are people's thoughts on using "_t" as a suffix for types in C/C++, as this source does? My understanding is that it is technically reserved for language defined types, but I haven't come across a reasonable alternative (and having no suffix makes for odd code, for example a custom type for a "viewport" is better imo as "viewport_t viewport;" than "viewport viewport;")
I don’t believe that’s a c language thing, but a posix thing. anyway, in my own code for myself I do it, but it is frequently forbidden in coding standards I’ve worked under (even in a non posix environment) and it doesn’t rise to the level of pushing back on it usually, I’ll name types however the style guide wants, even if it’s worse. t prefix, worse. The whole word typedef, worse.
From what I understand, it's POSIX that reserves the _t suffix, not C.
That being said, I picked up the _s/_t habit from working on idtech codebases, and decided that I'm fine with my own C codebases forever having a "not POSIX compliant" stamp on it, whatever that means in practice. For C++ I just use InitialCamelCase.
Probably not everyone's cup of tea, but I tend to use CamelCase for types, and under_scores for variables. It does result in constructs like 'Viewport viewport;' but at least it's easy enough to tell that Viewport is the type.
Mixing CamelCase type names and snake_case variable names feels chaotic to me, but it’s the standard coding style for Python, Ruby, Rust, and Google’s C++ code. I wonder who used it before Python.
Where I work the style guidelines for C specify to use "_type". Interestingly enough, the huge code base for embedded products with a custom OS does not rely on <stdint.h> types for fixed-width integer types: "uint32_t", for example. Instead, a company-wide header defines "uint32" etc. without the "_t" suffix. I don't know what guided that decision.
For a very long time the fixed-width types in stdint.h were not universally available, or in other headers, especially Visual Studio trailed behind. I guess this problem has stuck even after all C compilers caught up with C99.
It's totally fine and legal. Only POSIX reserves the _t for types, but POSIX is not the C standard (but even when writing code for POSIX, it's not like POSIX changes much these days, so unexpected type collisions because of POSIX updates will be very unlikely).
I've being using it for a long time and the only time i had a problem was when i ported an old 3d game engine of mine to Mac OS X and i had a type "key_t" which conflicted with some Mac header.
I just used the preprocessor to #define key_t mykey_t (or something like that) after the Mac-specific headers in the couple of files where the conflict was and never thought about it again.
Recently I was musing to myself about adopting the convention `func_r`, to denote "the thing that the function `func` returns".
Why spend the energy trying to think up two names (one for the function you're writing and one for what you should call the type of its result)? It's also amenable to preprocessor macros, if you're so inclined.
The code is clear, coherent, and straightforward. I’m not going to say it’s the best source code I’ve ever read, but it set a high bar. I’ve read source code to games since then, and I’ve seen all sorts of weird stuff… I’ve seen functions with nesting levels that go past the right side of the screen, I’ve seen functions a mile long that do a million things, you know. It was an early lesson that you could do cool things with simple code.
We used Quake and Quake II to teach a VR class to kids in the late 1990s and early 2000s. You got a VR headset, you got a half-dozen PCs with Q.E.D. or Qoole, and you taught a room of 12-14 or 14-18 year-old students how to make their own virtual reality game.
“Make your own virtual reality game” was, essentially, making your own Quake or Quake II level, without any monsters or guns in it. The story for the game could be told through on-screen messages triggered in the level. Students made all sorts of levels. One group made a level with slime you had to cross, and a button that turned on pumps to drain the slime (very much like what you would see in a standard Quake II level). Another group made a 3D maze out of water that you had to swim through, floating in space. I still have some of these student-made levels on my hard drive, after all these years, although I have trouble getting some of them to work.
For the class, I made a mod that gave the players a flaregun instead of the blaster. Basically, it was still the blaster, but with a different sound effect and no weapon model. I modified the blaster bolt to be affected by gravity, bounce off of things, and go out after a certain amount of time.
(If you were in that class—you may not have gotten the headset. We couldn’t always get it to work.)
This, of course, makes perfect sense as an output from an industry leader in the field (Carmack). However it definitely requires a lot of foreknowledge and, dare I say it, a mathematical bent to follow with confidence.
Guilty as charged!
I used to chronically write macaroni code. So you would have like 25 lines of code that ended up being the back bone of like a dozen things that would have another dozen things stacked on top of each. Would execute super fast BUT would amount technical debt quickly and eventually said functions would become untouchable because you would risk breaking a lot of stuff build on top of stuff.
Comments would try to clear it up, but communication skills where not the best. Explain function but not workings. Things like - "Does SINE table". "Table Defrag". "Binary resolve" etc.
Get a lot of people going "What the F*K is this shit?! But... it does the job damn fast". If it was any field other that video games, I would not have lasted long and neither would the product...
What's surprised me is that successful, fun to play, and stable enough games have been shipped with such absolute unmitigated disasters behind the curtain.
It makes me question sometimes all the effort(and time) I put into preventing the chaos, when such carelessly bodged together garbage can be perfectly profitable.
You can start with a great plan, but no plan survives gameplay- and balance-tweaking for very long.
Ideally the lower layers are much more structured though, usually those need to be maintained over a longer time and across games.
The first example makes it very clear that condition A means do it again and condition B means exit this loop while condition C establishes the boundaries of the loop.
I'd say I prefer the original.
Also I doubt everyone would agree that the one exit condition is more clear. For example, if I know that if e->inuse is 0 it will continue with the next round. Arguably it's more difficult to understand that from the single combined expression—because it isn't so: if !e->inuse but e == ent->client->chase_target, the original will loop but yours will exit.
Though I'm guessing ent->client->chase_target->inuse is probably never 0.
The best reason not to re-write for style reasons is you break it.
Deleted Comment
----------------------------------------- John Carmack's .plan for Jan 31, 1997 -----------------------------------------
I went down to the ferrari dealership today with Ann and American with the intention of buying a new f355 as a "sensible" car (in contrast to my other twin turbo monstrosities).
There was an F40 sitting out front.
Umm. Sensible car. F40. Sensible car. F40. Hmmmm.
American played the evil conscience and Ann played the good conscience. For a while. Then Ann got infected by the Dark Side. :-) I bought the F40.
Ok, I now have too many ferraris. I have a plan to fix that. This hasn't gone through all the legal crap necessary yet, but it is my intention to give away my first ferrari as the grand prize of a Quake tournement.
Yes, I am serious.
DO NOT SEND ME ANY MAIL ABOUT THIS! When we have more to say about it, we will make a formal announcement. This would be a good time to start a heated debate in the newsgroups about what the fairest tourney rules would be, though.
The specs:
1987 328 GTS with turbocharged engine. Feature in the january, 1994 issue of Turbo magazine. Made 360 HP at the rear wheels on a chassis dyno. New engine (I melted the first one...), new paint. The interior could use a little work.
I plan to also include enough cash to cover tax and insurance.
After 2 or 3 purchases, the dealer habitually put the most expensive car in that spot.
When I started, I had only ever played the original DOOM, so I used those controls, and lo and behold they worked, and I thought nothing of it. At some point as I was playing along, my view suddenly became locked to the ceiling and I was thoroughly confused and stuck. Turns out I had bumped the mouse, and managed to figure out that mouse look was a thing after extended fiddling with the system to try to determine the cause and get back to killing badguys. Needless to say, the game got quite a bit easier after that.
There wasn't internet, neither I nor my friend knew what mouse-look was, we had never seen anyone play a true 3d fps. It is hard to imagine how obscure and alien the interfaces for games are because of how pervasive the knowledge has become.
Glad to see the source code released!
In case you were unaware, this is actually the source code of the rerelease of Quake II. The source code for the original Quake II has been released for many years[0], along with many of the id Software classics[1].
[0]: https://github.com/id-Software/Quake-2
[1]: https://github.com/id-Software
If it's anything like the Quake re-release, it's not actually using idTech 2 at all.
I was at a ComicCon in Rome, Italy, circa 1999, anyway Half Life just hit gold production and released multiplayer demos there were at least 20 people all playing on lan multiplayer half life, with mouses too! Crazy.
I went to many many lan parties since then up to 2005, then online match making killed quakenet and socializing.
Back in 1999, Quake II was the game I played (like, literally the only one). I sunk so much time in QII and I wrote my very first code building a Mod.
I think it's fair to say, Quake II turned me from a computer user into a computer programmer. And set the course for my entire adult life and career.
From my original mod, I learned how to calculate vectors, learn an existing code base, integrate autobuilds for rapid testing.
I also dabbeled in making maps, learning 3rd art, but, quickly realized I am no artist or designer.
Sadly, my version control and backup policies at the time were non-existant and I've lost all my original code.
The next game that really got my attention in the same way was Tribes 2, the scriptings and modding you could do there was truly amazing.
https://xn0.co/Xeno-Mod_1.9_Setup.exe
Because I'm sure everyone on HN just downloads execs from a random internet source and runs them!
But if you have the OG QII, are curious, it is safe to run.
Then you could even do: npx run quake-2-xeno-mod@1.9
Not true, some of us have JavaScript disabled.
...should I not?
An homage: http://guake-project.org/
Using one of my dad's ancient parallel cables and fooling Windows into thinking it was a LAN connection, which worked well enough, we played for days, until it got kind of tedious. So we then got into the whole modding scene - not creating anything, but downloading and testing the things which were out there. I will never, ever forget Girobot and KQP (Killer Quake Patch).
The Girobot bots were a force to be reckoned with when they didn't get stuck in the terrain. It was KQP which added all kinds of crazy mods/weapons/bot systems to the game that was the real gold. The Vampire Gun left us in stitches every time someone was hit by it, because it basically guaranteed a slow but not too slow death in which the hit player would crumble into little chunks of flesh. I miss the nineties very, very much.
Also, I believe KQP had ZeusBots included (for reference).
[1] https://github.com/Jason2Brownlee/QuakeBotArchive
First few computers were too slow to run Quake effectively, but then I had a Duron 900 (they ran slow AND hot!), bought a 'PC Powerplay' magazine that came with a CD full of quake mods, and a map with 20-40 of ZeusBots worked just fine!
With a null modem? I had same setup with my college roommate in our apartment. This was mid 90s, so it was Doom 2 and other FPS from that time.
Great memories.
I'm assuming they don't have legal requirements since they owned the old code anyway.
Deleted Comment
I wish there were a law that if you don't release a patch for your software for 20 years to make it workable for newer platforms, you've to release it as open source from the perspective of cultural significance.
That being said, I picked up the _s/_t habit from working on idtech codebases, and decided that I'm fine with my own C codebases forever having a "not POSIX compliant" stamp on it, whatever that means in practice. For C++ I just use InitialCamelCase.
I just used the preprocessor to #define key_t mykey_t (or something like that) after the Mac-specific headers in the couple of files where the conflict was and never thought about it again.
Why spend the energy trying to think up two names (one for the function you're writing and one for what you should call the type of its result)? It's also amenable to preprocessor macros, if you're so inclined.
Cube instead of cube_t.