RSS ☼

erysdren's WWW site

software, gameware, strangeware


Changelevels in FTEQW
2025-03-10

The FTEQW engine offers many improvements in changelevel functionality over the original Quake engine from 1996. In the original engine, your only way to send player data across a level transition was through the parm globals in the QuakeC server code, which meant you had exactly 16 floating-point numbers to use to store any data the players needed to remember across a level change. Everything else about the player's state was dropped. This worked well enough for Quake, but is severely limiting if you need to send anything that couldn't fit into a float.

Not only does FTEQW offer 64 parm globals instead of 16 (for DarkPlaces compatibility), it also has a parm_string global which can store a theoretically unlimited amount of string or buffer data. In this document I'll show you how I decided to use it for my projects.

When the player spawns in a level for the first time, the function SetNewParms() is called in the SSQC progs. This is where you set the default parm values for every player that spawns in a level. For example, let's say that parm1 is going to store the player's health value across a level change. You would set the default health value like so:

void() SetNewParms =
{
	parm1 = 100;
};

When the engine is sending the players through a level transition, it calls the function SetChangeParms() for each player entity in sequence. For storing the player's health, you would do it like so:

void() SetChangeParms =
{
	parm1 = self.health;
};

But, as stated before, this can get rather limiting if the player has data that doesn't fit into a floating point number, such as a dynamic inventory of items and weapons or an stats tree or something. This is where parm_string comes in. If you're using a modern version of FTEQW and the FTEQCC compiler, you also have access to several JSON parsing builtins. We will be using these to make the usage of parm_string easier.

If we wanted to store more complex data in parm_string, the default value for it should be a valid empty JSON node, like so:

void() SetNewParms =
{
	parm1 = 100;
	parm_string = "{}";
};

And when SetChangeParms() is called, you will write your extended data as valid JSON entries in parm_string, like so:

void() SetChangeParms =
{
	parm1 = self.health;
	parm_string = strcat(
		"{\"some_extended_field_string\":\"",
		self.some_extended_field_string,
		"\"}"
	);
};

Note we are using the strcat() builtin to construct our JSON string rather than sprintf(), because sprintf() seems to have some difficulties with very long strings.

The above code will store a per-player string value called some_extended_field_string into parm_string for storage across the level transition. Ideally you'd wanna make some helper functions to easily write and format your JSON, but this is just a basic example that constructs it directly.

Now the interesting part comes when it's time to read the data back. You'd want to do this in the function PutClientInServer() after setting up your fundamental player class, like so:

void() PutClientInServer =
{
	// setup fundamental player state
	self.takedamage = DAMAGE_YES;
	self.solid = SOLID_SLIDEBOX;
	self.movetype = MOVETYPE_WALK;
	self.fixangle = TRUE;
	setsize(self, [-16, -16, -36], [16, 16, 36]);
	self.flags |= FL_CLIENT;
	self.view_ofs = [0, 0, 28];
	self.classname = "player";

	// load health value from parm1
	self.health = parm1;

	// parse parm_string as json
	jsonnode root = json_parse(parm_string);
	if (!root)
		error("couldn't decode parm_string as JSON!");

	// check if we have the field
	// if so, copy it out as a string
	if (root["some_extended_field_string"])
		self.some_extended_field_string = root["some_extended_field_string"].s;

	// clean up
	json_free(root);
};

You don't have to use parm_string as JSON, though. You could just print the values as space-separated tokens and use the tokenize() or tokenize_console() builtins to read them back. Either way, it's a good method for storing lots of custom data across level transitions. Enjoy!