Text manipulation in Quake (part IV): State

Solutions

Answers to Qs 1 and 2 modify the S_Print_Splitflap function, I ended up with this:

float S_Print_Splitflap_Gate;
float S_Print_Splitflap_Floor;
void(float c) S_Print_Splitflap =
{
	if(c > S_Print_Splitflap_Gate && c < 128)
		c = S_Print_Splitflap_Gate;

	if(c < S_Print_Splitflap_Floor)
	{
		c = S_Print_Splitflap_Gate; //solution to Q1
		if(c > 127)
			c = ' '; //solution to Q2
	}
	WriteByte (MSG_ONE, c);
}

The code I added for Q3 was S_Print_Splitflap_Floor = time * 20 - 100;, which starts removing characters at 5 seconds in, but doesn’t replace any in the printed phrase until a second or so later. When I look back on the effect from the homework, it’s not quite the right way to do a split-flap display. The higher value character shouldn’t be waiting to move until the lower ones catch up – they should all be increasing by one space until they get to the ' ' at the end. You can implement this version of the function for extra credit : – )

State

So last time we made our text animate into place like a split-flap board. I’ll admit that I have a small fascination with these things after seeing one in an art installation about terrorism, but I can see that it’s not the most obvious thing you’d want to do with text. Why not just create text that appears character-by-character as if it was being typed?

It turns out that doing so is a bit harder than the split-flap printing. You certainly need a parameter to tell the function how many characters to print this frame, so everything up to the last post is necessary. However, we also need to keep track of how many characters we’ve printed so far to make that useful. Normally when we want a function to maintain state we add a local variable. What goes wrong in S_ functions?

Well, we’ve inverted control, so the S_ function locals get reset after each character, when control returns to the T_ function. So we need some other way to store state, and like last time we’re going to use a global! Again, the warnings that this prevents us from recursive calling of our function apply. Start with the code base from last time, and define a pair of global floats called S_Print_Cursor_Limit and S_Print_Cursor_Count. The former will be the parameter to our S_ function setting how many characters to print, and the latter will be the state variable that tracks how many we have printed so far.

void(float c) S_Print_Cursor =
{
	S_Print_Cursor_Count = S_Print_Cursor_Count + 1;
	if(S_Print_Cursor_Count > S_Print_Cursor_Limit)
		return;
	else if(S_Print_Cursor_Limit-S_Print_Cursor_Count<1)
		WriteByte (MSG_ONE, 11); //char 11 = cursor
	else
		WriteByte (MSG_ONE, c);
}

There’s an extra flourish in this function: if a character is exactly at the limit position, it gets replaces with the cursor character instead. Once the limit exceeds the length of the text then the cursor disappears. Notice that when our count exceeds the limit, all we do is return without printing anything to the screen. This lets us skip the end of the text, but the T_ function still iterates over all the remaining characters needlessly. Efficiency is not the watchword of this kind of code, because it’s hard to do it in QuakeC at all!

Unfortunately writing our S_ function is only half the battle. Find our usual location for text code in PlayerPostThink and replace it with the following:

	msg_entity = self;
	WriteByte(MSG_ONE, SVC_CENTERPRINT);
	S_Print_Cursor_Limit = time * 6 - 12;
	S_Print_Cursor_Count = 0;
	T_Location(S_Print_Cursor);
	WriteByte(MSG_ONE, 0);

As in today’s solution code, we are using negative values of S_Print_Cursor_Limit to our advantage – in this case to delay the animation a few seconds so that the player can see it all. We’re ready to test in-game, load it up and the text types across the screen. Wonderful! Although we’ve used it here to create an animation, this function is essentially how you create a substring in our text system, and so our toolkit expands.

Looking at our code in PlayerPostThink, it’s a bit of an ugly lump. Our iterator functions and output functions are nice, interchangeable components, but we are calling them with a rats nest of different bits. We have to set up the centerprint with boilerplate code, and tear it back down again with more. On top of that we’ve got a parameter that we have to set, and this post has introduced a state variable that we must remember to reset each time. Next time we’ll look at another programming concept called coroutines, and find a way to tidy away some of the mess.

Homework

1) Eagle-eyed readers might have noticed we didn’t use the idiom of closing our centerprint with S_Print_Cursor(0);. Why not?
2) At the moment our truncated string is centre-aligned, which makes it scroll left as it appears. Modify S_Print_Cursor so that the string is padded to its full length with spaces.
3) S_Print_Cursor currently uses two global variables, but it is possible to combine them into one. Can you see how? Can you implement it?

Advertisements

One thought on “Text manipulation in Quake (part IV): State

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s