Field Pointers in QuakeC

Field pointers are a topic with a scary name! Don’t be put off by the word “pointer” – there’s no way to do pointer arithmetic in QuakeC, so we won’t have to do anything scary with them. Field pointers will prove to be surprisingly powerful, they let us write generic code in QuakeC, which stops us from doing a bunch of copy-pasting.

I’m going to share a snippit of code from Quoth to illustrate them today. It comes from the rubble template code, where we want to populate some default values for the bits that spawn, but let mappers override the default on any of the fields which they set. So we start with some code like this:

if(self.preset == 1) //wood
  {
    if (self.noise1 == "")
        self.noise1 = "impact/wood_i1.wav";
    if (self.noise2 == "")
        self.noise2 = "impact/wood_i2.wav";
    if (self.noise3 == "")
        self.noise2 = "impact/wood_i3.wav";
    if (self.noise4 == "")
        self.noise4 = "impact/wood_i4.wav";
    if (self.noise == "")
        self.noise = "impact/wood_bk.wav";
    if(self.modelpath == "")
        self.modelpath = "progs/plank.mdl";
  }
  else if(self.preset == 2) //flesh...

That code only covers one template, out of 10 available to date. The whole template section is difficult to read – firstly because it makes the code very long. Secondly there’s all this indentation, so your eyes are darting back and forth trying to read what’s going on. Lastly the code is doing the same boilerplate thing with a bunch of different fields, and the boilerplate is a distraction. What we’d like to be able to do is look at this code, and focus on the important thing – the sounds and models which belong to this template. At the moment it’s hard to even check it’s correct – did you spot the deliberate mistake?

I hope you went back and found it! In line 7 we checked if the mapper had set a value in self.noise3, but if she didn’t then we set the default in self.noise2 by accident (line 8)! This would be very easy to do when copy-pasting all those templates.

To begin improving it, let’s look in the code for what we are repeating, line after line. Each if statement goes:
if generic field is not set to anything, set generic field to a default value.
When we repeat the same pattern again and again in our code, we should define a function to perform the repetition. Let’s name the function SetStringDefault. Because our repeating pattern has a fill-in-the-blanks element, we need to give SetStringDefault parameters which contain the values to fill in. It’s fairly obvious how to create a parameter for the default value, just pass a string to the function. But how do we tell the function which field to use?

The answer is field pointers! Looking at it from a caller’s point-of-view first, we end up with:

SetStringDefault(noise3, "impact/wood_i3.wav");

From this end, a field pointer is just the name of the field. Our template code reaps the benefits: it’ll halve the size immediately. All the lines will be formatted identically, making it easy to focus on the field-default pairings. And there’s no way to mismatch the field we test and the field we set, as there’s only one place to specify the field.

So how do we declare a function which accepts a field pointer? It’s quite subtle, so read carefully:

void (.string generic_field,
      string default_value) SetStringDefault =

The default_value parameter is a regular string, but generic_field is a field pointer – notice that the type in front of it is “.string” with a dot, rather like if we were defining a field. We can call it a pseudo-field for now, to remind ourselves that it’s “standing-in” for a field, it’s not a real field itself. This is very important to remember once you see how we use it:

void(.string generic_field,
          string default_value) SetStringDefault =
{
  if(self.generic_field == "")
    self.generic_field = default_value;
}

We treat the pseudo-field exactly like we would any normal field, and just look up the corresponding value on the self entity. Notice that we can assign to it in the usual way as well. Can you work out what SetFloatDefault would look like? Have a go before you look at the code below.

void(.float generic_field,
     float default_value) SetFloatDefault =
{
  if(self.generic_field == 0)
    self.generic_field = default_value;
}

Did you get all the changes? Apart from the new function name, we had to change the generic_field type to “.float“, and the type of the default_value to float as well. We also had to change how we test if the field was not set.

A third function which I have found to be useful often is SetPositiveDefault:

void(.float generic_field,
     float default_value) SetPositiveDefault =
{
  if(self.generic_field <= 0)
    self.generic_field = default_value;
}

This is useful for fields like delay and wait, where it does not make sense to allow negative values. The function replaces negative values with the same default value as zero values. There’s a fairly common pattern among fields on quake entities, where the default value (replacing zero) is a positive value, but if you really wanted zero delay you can get it by setting a negative value. One approach would be to call these two function in sequence:

  SetFloatDefault(wait, 2);
  SetPositiveDefault (wait, 0);

Because we’ve already replaced zero values by the time we reach the second call, we don’t have to worry that SetPositiveDefault will see an entity where self.wait == 0. We could instead write a function

void SetDefaultOrNegative(.float generic_field,
                          float default_value,
                          float negative_value)

(left as an exercise)

Although these functions shine brightest when you’ve got loads and loads of defaults going on…

    SetStringDefault (noise1,    "impact/wood_i1.wav");
    SetStringDefault (noise2,    "impact/wood_i2.wav");
    SetStringDefault (noise3,    "impact/wood_i3.wav");
    SetStringDefault (noise4,    "impact/wood_i4.wav");
    SetStringDefault (noise,     "impact/wood_bk.wav");
    SetStringDefault (modelpath, "progs/plank.mdl");

…they are also valuable in all your spawn functions. Not only do they save a line of code here and there, but they are self-documenting. It makes the intention of that bit of code very clear – “this section is setting a default”. So while the most important lesson today is what a field pointer is and how to use one, if you just take these Set___Default functions away and update your code to use them, it’ll be made nicer for very little work!

One thought on “Field Pointers in QuakeC

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.