Quake Strogganoff: Bitflags

Fri, Jun 19, 2020 5-minute read

If there’s any important lesson of which to learn, knowing how bitflags work is probably somewhere near the top. In QuakeC, this is used to create spawnflags for entities and can also be thought of as a container of sorts to store other entities on an entity like a suitcase, only this suitcase is a float. And it can carry a maximum of 23 items. Why 23? In short, math.

For a better understanding as to why 23, see the movie “The Number 23” starring Jim Carry during his ‘I’m a serious actor phase’ which was just after his ‘character actor phase’ and right before his ‘I’m out of my fucking mind’ phase (where we currently reside at the time of writing). Seriously though, you can google bitwise operations if you want to go more in depth (and I suggest you do) however for this article, I don’t want to get bogged down in a whole ball of confusion and want to keep the focus on how to actually use them in QuakeC.

So, let’s get straight to the biscuits. In defs.qc, you will find a section that looks like this (or similar, maybe with float instead of #define):

// items

#define	IT_AXE					4096
#define	IT_SHOTGUN				1
#define	IT_SUPER_SHOTGUN		2
#define	IT_NAILGUN				4
#define	IT_SUPER_NAILGUN		8
#define	IT_GRENADE_LAUNCHER		16
#define	IT_ROCKET_LAUNCHER		32
#define	IT_LIGHTNING			64
#define	IT_EXTRA_WEAPON			128

#define	IT_SHELLS				256
#define	IT_BULLETS				512
#define	IT_ROCKETS				1024
#define	IT_CELLS				2048

#define	IT_ARMOR1				8192
#define	IT_ARMOR2				16384
#define	IT_ARMOR3				32768
#define	IT_SUPERHEALTH			65536

#define	IT_KEY1					131072
#define	IT_KEY2					262144

#define	IT_INVISIBILITY			524288
#define	IT_INVULNERABILITY		1048576
#define	IT_SUIT					2097152
#define	IT_QUAD					4194304

If you add them all up, you’ll get 23 items that were used in the original Quake which even includes a bonus extra weapon for mods to use which is nice.

You may have noticed that IT_AXE is 4096. This is done to annoy anyone with OCD and the order of these actually aren’t important, it’s just for readability. The important pattern to note is the flag doubling each time.

These flags represent their respective items and are stored in:

.float		items;			// bit flags

As maybe covered before, .float is known as a field type, meaning all entities can be given any of the above items which is stored as a field float like self.items with 23 slots in the bag.

So how about putting something into self.items?

self.items = self.items | IT_ROCKET_LAUNCHER;

That will change the bit and essentially give the entity a rocket launcher (this is what happens when a player picks up an item). It’s like turning on a switch that says “the slot holding IT_ROCKET_LAUNCHER is now occupied”.

You could then check to see if the slot is occupied by seeing if self.items = self.item & IT_ROCKET_LAUNCHER is true, and if so, run some code that only the holder of a rocket launcher can do.

For fun (or not): try modifying func_button to only activate if the player is carrying a rocket launcher.

Alternatively, you can remove an item using:

self.items = self.items - (self.items & (IT_KEY1 | IT_KEY2 | IT_INVISIBILITY | IT_INVULNERABILITY | IT_SUIT | IT_QUAD) );

Found in SetChangeParms in client.qc. This ensures any powerups and keys don’t cross over when the player crosses over to a new level.

Doors use this as well, a gold key door is essentially a door that holds IT_KEY1 in its own self.items. When the player bumps into the door, it checks to see whether the player also has IT_KEY1 in their self.items, and if so, will open, otherwise it’ll return a message saying that a key is required. Then it removes the item from the player’s inventory.

In doors.qc under door_touch:

NOTE: other in this case is the entity that is touching the door and self.owner is the door entity itself, since doors are made up of several entities such as a trigger volume.

if ( (self.items & other.items) != self.items )
{
  if (self.owner.items == IT_KEY1)
  {
    if (world.worldtype == 2)
    {
      centerprint (other, "You need the silver keycard");
      sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
    }
    else if (world.worldtype == 1)
    {
      centerprint (other, "You need the silver runekey");
      sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
    }
    else if (! world.worldtype)// == 0)
    {
      centerprint (other, "You need the silver key");
      sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
    }
  }
  else
  {
    if (world.worldtype == 2)
    {
      centerprint (other, "You need the gold keycard");
      sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
    }
    else if (world.worldtype == 1)
    {
      centerprint (other, "You need the gold runekey");
      sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);			
    }
    else if (! world.worldtype) // == 0)
    {
      centerprint (other, "You need the gold key");
      sound (self, CHAN_VOICE, self.noise3, 1, ATTN_NORM);
    }
  }
  return;
}

other.items = other.items - self.items;

NOTE: Quake has different world types to re-use keys with different messages and models. Original source may look a bit different (as this is 1.06 recoded) but it’s all essentially doing the same thing.

In the door’s spawn code, the item is set via a spawnflag:

if (self.spawnflags & DOOR_SILVER_KEY)
  self.items = IT_KEY1;
if (self.spawnflags & DOOR_GOLD_KEY)
  self.items = IT_KEY2;

Door spawn flags are the same as defining items:

#define DOOR_START_OPEN		1
#define DOOR_DONT_LINK			4
#define DOOR_GOLD_KEY				8
#define DOOR_SILVER_KEY			16
#define DOOR_TOGGLE					32

Hopefully this has made some sense in terms of how items are held, given and taken and even compared between different entities for a useful purpose.

For a much more coherent and in depth article regarding bitflags, check out this article by Marco Hladik.

Happy modding!