I think I've solved an old character scripting issue

This is a discussion / support forum for the Hugo programming language by Kent Tessman. Hugo is a powerful programming language for making text games / interactive fiction with multimedia support.

Hugo download links: https://www.generalcoffee.com/hugo
Roody Yogurt's Hugo Blog: https://notdeadhugo.blogspot.com
The Hugor interpreter by RealNC: http://ifwiki.org/index.php/Hugor

Moderators: Ice Cream Jonsey, joltcountry

Roody_Yogurt
Posts: 2255
Joined: Mon Apr 29, 2002 6:23 pm
Location: Milwaukee

I think I've solved an old character scripting issue

Post by Roody_Yogurt »

Instead of just saying the problem (and its fix) right away, I'll begin with all of the background information.

After finishing a couple small Hugo projects earlier this year, I decided to go back to the task of adapting Robb's Fallacy of Dawn to use my Roodylib library. I had previously done this with Necrotic Drift (and possibly Cryptozooker, I can't even fully remember), but FoD is a bit of a larger undertaking that took more preparation to tackle. Anyhow, none of these Roodylib-ized versions were made for public consumption. The main point of doing them is to see if the Roodylib-ized versions function, and if they function, if they are configurable enough to keep all formatting and such the same. Other people's games are really the best way for me to discover shortcomings in Roodylib.

I've had a version of FoD-with-Roodylib that could compile for a long time now. Testing it is the thing that I've really been putting off. So, I made compiled versions of each with normalized random numbers so the same commands should get all of the same responses. Then I made a recording file that went as far as a third (or possibly a quarter) into Robb's original. The walkthrough I was using (it has been a long time since I've played through FoD) didn't have individual commands, and I didn't have the game map handy so I got lost in the city and decided to stop there.

When I used playback on the Roodylib version, it didn't take very long for the recording to go off the rails. My lovely dinner at the local sushi restaurant never got interrupted by an uninvited guest, which is required for the game to proceed, so FoD's protagonist was forced to spend the rest of the recording (and his life) trapped in that sushi restaurant.

Using Hugofix's script monitor, it turned out that the assailant was waiting at a point where he should have been barging in. In retrospect, I should have instantly realized that the end of his script had been overwritten by the beginning of a script for the character, Clara (which is started at the same point in the game).

The Hugo Book implies that a "step" is actually two elements in the setscript array, but the code behaved like it was one. The assailant's script had 19 steps but would go off the rails after step 16. Since the Hugo Book and the constant declared MAX_SCRIPT_STEPS as 32, it really felt like, "gee, maybe there is one element to a step after all."

So, first I got the assailant's script working by splitting it up into two scripts, adding this code to his object:

Code: Select all

	after
	{
		actor CharMove
		{
			if parent(self) = BoggitStreet3
			{
				CancelScript(self)
				dapper_script2
				return true
			}
			else
				return false
		}
	}
This worked, but at this point, I realized that the reason the original code worked in Robb's version was that hugolib.h had been edited with new values for all of the script constants and array sizes (MAX_SCRIPTS, MAX_SCRIPT_STEPS, scriptdata[], setscript[]). None of these things use "#if undefined" in hugolib.h so it didn't seem like Kent thought these things needed to be modifiable. Things didn't seem like they were working like they were supposed to with the hugolib's code.

I once fixed a math error in Hugo's score ranking system, and I thought that maybe something like that was going on here, too. To figure out exactly how things were supposed to be, I broke it down logically. setscript[] is the array that holds all of the character routines and the possible object being used by the routine (example: &CharMove, n_obj). setscript[] is defined with 1024 elements. The MAX_SCRIPTS constant is how many scripts can run simultaneously. It's defined as 16, so dividing 1024 by 16, we get 64. Since MAX_SCRIPT_STEPS is 32, this is the proof that a "step" is two elements (the character routine and the object), not one.

So I then looked over the Script and RunScripts routine to find places that were using the wrong logic. The Script routine is used to return a number where a new character script should start in the setscripts[] array. Hugolib has this line.

Code: Select all

	return o * MAX_SCRIPTS * 2
In this code, o will be a number ranging from 0 to 15. Knowing that 64 elements are meant to be used for each character script, we want the routine to return values like 0, 64, 128, and so on. Since MAX_SCRIPTS is 16, it was returning values like 0, 32, 64, etc. In my replacement routine (which will be added to future releases of Roodylib, I have changed it to this:

Code: Select all

	return o * MAX_SCRIPT_STEPS * 2
RunScripts is the routine that reads the setscript[] array each turn and executes whatever needs to be done, so I had to make sure it was looking in the right place, too. Hugolib had this code:

Code: Select all

			! action
			routine = setscript[a * MAX_SCRIPT_STEPS + step * 2]

			! object being acted upon
			b = setscript[a * MAX_SCRIPT_STEPS + step * 2 + 1]
The problem was that MAX_SCRIPT_STEPS wasn't getting doubled (to make sure we are looking at groups of 64). It really should have been "routine = setscript[a * MAX_SCRIPT_STEPS * 2 + step * 2]". So I just used the transitive property to have my replacement routine do this:

Code: Select all

			! action
			routine = setscript[(a * MAX_SCRIPT_STEPS + step) * 2]

			! object being acted upon
			b = setscript[(a * MAX_SCRIPT_STEPS + step) * 2 + 1]
Long story short, Robb's 19-step-assassin script now works as is. It's possible that I've missed something else, but so far, I'm feeling pretty optimistic about this.

Not that so many games are written in general, but it's even rarer to have ones with multiple characters moving around with scripts longer than 16 steps where this kind of bug came up. Still, it's surprisingly satisfying to solve an issue that is nearly 20 years old already.

My most recent Roodylib release had a little bug in it, so it's nice to have a bigger reason to get a new update out there sooner!