Scripting in Quake 2

Over the past two weeks I’ve been working on a game using Quake 2’s engine, Basilisk Xorsonim. The game part hasn’t come anywhere yet due to a lack of resources, so while I ponder how to motivate myself to make or acquire models, textures, sounds, music, levels, designs.. uh.. Well, everything for a game that should look at least half way decent, I’ve been working on the engine to make sure it’s as easy to work with as possible with decent pipelining, build system and, of course, code.

In general, Quake 2 is a great engine code-wise. I had to rewrite the build system, and I had to fiddle with configuring the fork I’ve been working off of to enable things like zip file support and PNG support. When all that was done, and I had reacquainted myself with the modifications I had previously (about a year ago) made to the engine, the first thing that stuck out to me was that it was extremely difficult – and quite annoying – to add, modify, or even delete entities in the game code.

Quake 2’s game library code is a bit of a mess, contrasting heavily with the clean and blissful server and client. If I had to guess, at least 3/4ths of it was written by the third party producing the game itself rather than id Software’s cast of geniuses. But it could be one of many reasons that it’s rather unclean, and more importantly: extremely hard-coded. Ignoring the fact that the game library is pure C (a great thing) and has no external scripting or ‘soft-modding’ to speak of, the design inside the game library barely allows for modularity or quickly iterating ideas in code. It feels quite rushed compared to everything else.

The obvious solution to a knife fight is to bring a shotgun. Or an anti-materiel rifle. There’s no kill like over kill, so what I’ve done to fix this has been embed a scripting engine and port all of the game code to MoonScript, simultaneously cleaning up the API along the way. Most of it was tedious rather than difficult, and I even started writing a Squirrel backend before having to throw it out because it refused to work under any circumstances.

So, everything’s run-time now. Animations, entity registration, etc. Entities have a table associated with them that can hold any arbitrary data, reducing the memory footprint of simpler entities with no real run-time cost. Entity fields are grabbed from the engine with a nearly constant-time lookup (calculated ahead of time) and engine functions are exposed mainly as-is, while taking advantage of Lua features like multiple return values. Here’s a sample of my port of monster_infantry from Q2:

SP_edict_test = (ent) ->
   return nil if deathmatch!

   export sound_pain1 = soundindex("infantry/infpain1.wav")
   export sound_pain2 = soundindex("infantry/infpain2.wav")
   export sound_die   = soundindex("infantry/infdeth1.wav")

   export sound_gunshot     = soundindex("infantry/infatck1.wav")
   export sound_weapon_cock = soundindex("infantry/infatck3.wav")
   export sound_punch_swing = soundindex("infantry/infatck2.wav")
   export sound_punch_hit   = soundindex("infantry/melee2.wav")

   export sound_sight  = soundindex("infantry/infsght1.wav")
   export sound_search = soundindex("infantry/infsrch1.wav")
   export sound_idle   = soundindex("infantry/infidle1.wav")

   e = edict_of ent
   self = :e

   -- Physics
   e.movetype   = MOVETYPE_STEP
   e.solid      = SOLID_BBOX
   e.modelindex = modelindex("models/monsters/infantry/tris.md2")
   e.mins       = {-16, -16, -24}
   e.maxs       = {16, 16, 32}

   e.health = 1000
   e.mass   = 200

   -- Callbacks
   e.pain   = pain
   e.die    = die
   e.stand  = stand
   e.walk   = walk
   e.run    = run
   e.dodge  = dodge
   e.attack = attack
   e.sight  = sight
   e.idle   = fidget

   -- Init
   link_entity e.ent

   e.currentmove = move_stand
   e.scale = MODEL_SCALE

   start_walk e.ent

   self

object_type SP_edict_test, "edict_test"

The scripting engine is about 920 lines of code at the moment. It’s as optimized as a vanilla Lua 5.3 embedding can be, and should significantly speed up development… when I figure out what to do about resources.

There’s a few things I can think of that this could be used for besides making development easier, such as:

  • ECS (Entity Component Systems.)
  • Soft server mods.
  • More dynamic/procedural AI.
  • Map-specific monsters/weapons/decorations/etc.
  • Randomizing values or behaviour per map instance.
  • Creating randomly generated weapons.
  • Advanced map scripting.
  • Dynamically generating animation sets.

So, hopefully I’ll finish this project some day.

Worse Solutions For Problems People Who Are Knowledgeable Have Occasionally

Author’s note: This is not an up to date rant. It is for the most part still relevant, however.

I used to hate C++. And now I’m starting to feel that this hatred was well-placed.

The more I think about it, how my projects with it go, how hard it is to understand other people’s code, how much trouble C++ has given other people I know, the more I realize: C++ is just really garbage.

And, my biggest issue, lately, is that 90% of other languages are simply
*better* than it. As for the other 10%, I find the same or similar issues with them as I do C++. (That is to say, I don’t mean to rag on C++ specifically, but it is a great hitting dummy.)

Even Java – which I quite dislike for quite a lot of reasons – has something that C++ doesn’t: you can READ it. You can understand it. Even if it is insane, verbose, and awful all around, it is at least consistent in being awful.

The decision I’ve made is that I really just want to write everything in C. Or D. Or Ruby. Or all three. Fuck it, I’ll write shit in Go, I don’t care. I just don’t want to write object oriented code anymore. Not like C++ does it. And not usually like C# does it. Not like all the languages that take after its broken model and that influenced it do.

One thing is generics. Generics can go to hell. As I was first writing this, I opened the Wikipedia page for generic programming for some reference, and the first quote on the page is both fitting and a summation of my entire experience writing overly-generalized code over the years:

Dynamic, highly parameterized software is harder to understand than more static software.

— Gang of Four, Design Patterns (Chapter 1) (ISBN 0-201-63361-2)

Generic programming is an abhorrent concept. I’d maybe go as far as to say it’s broken. It feels like throwing your code in a blender just because you may want to use it a few times.

This is awful.

If you want to reuse your code, perhaps you should consider instead of
bloating your compiled code, bloating your headers, making your code so
extravagantly terse and crushing all dreams of a sane compile time, you should just carry out the effort to think, “Hmm, how will I use this? Then, how will other programmers use this, and how MIGHT they use this?”

It’s not even that hard, I’m sure most programmers have experienced that such questions are already answered in the back of your head when you’ve got enough experience on the subject of the code. Just think. THINK. That’s what you’re doing to write code; and that’s what you should be doing to plan it.

The complexity of C++ is caused by so many things like this. Concepts that
make you ask, “Why? Why go through the trouble of doing something that doesn’t even help you?” There is simply too much.

Another such example is its broken concept of the object. Object oriented
programming is NOT a broken concept. It’s the best thing when done right.

Just look to D for example. The combination of UFCS and all member functions being virtual is a perfect combination. Virtual functions provide a nicer interface to function pointers, letting you use functions as data with ease, and UFCS provides for the need of implementing functions for an object.

That’s all you need. Really. And please don’t say, “but that’s not no-cost!”. That’s exactly so. The concept of a member function should merely be so that it may give you extra information for the type. That’s why it’s IN the type. You are directly instructing the compiler to add more data to the object type, just that data is a function.

C++ does this wrong. A lot of languages do this in a way that I hate. The way of adding type info that has no purpose. Why bind member functions to the type? It becomes the same issue as generics – why? Why go through the trouble of putting crap in your type that’s just a regular old function?

Fuck.

Let me tell you something. C just works. C works well for a lot of people.
I almost want to say it works well for everyone, but that just isn’t ever going to be true. I think there are lessons that people ignored from C; or lessons that were unfortunately mistaken. Hell, I’m sure any language design geek would understand these.

C lacks concepts and paradigms of most high level languages. This limits the programmer to working with the small, yet solid, feature set C has; making her think before writing. Like any form of art. You could draw a picture with no forethought if you were using an overtly complex tool, but it would have the subtlety of a train crashing into a dynamite factory. It would simply lack the vision that your art needs to be truly *beautiful*.

Okay, I’m rambling, sorry. What I’m saying is this is a blessing just as much as a curse, your code follows a dead-simple feature set and therefore will be dead simple. Any idiot reading it will just get it. You must apply forethought, and forethought breeds both creativity and beauty.

These are all just random thoughts I’ve had on programming throughout the
years. I don’t intend to change anyone’s views on “what language is the best” because, really, that’s dumb. The best language is the one that can get your project done.

Except Python. Fuck Python.