The Middle of The End (Progress Report)

Less than two weeks left of coding. My summer break officially started, so I can spend copious amounts of time playtesting the game now!

So far, I’m about 9-10 hours into the game’s storyline (my save’s playtime says I’m 27 hours into it, but that’s due to me sidetracking into bug testing). This is around the halfway point of the game. Surprisingly, though, I did not find as many bugs as I thought I would, and most of the ones I did were harmless (except for one or two).

I’ll first talk about the bugs from the last blog post that I was able to fix and then proceed to present the newly discovered bugs.

Sound Effects Not Syncrhonizing

The sound system worked in SAGA2 at the start in the following way: we queued all sounds played, be they voices, sound effects, or music, and played them every time in the game loop. Music was redesigned to not use the queue system, and it looks like sound effects needed the same treatment.

Fix: Remove the check for when the sound effect handle is active.

	delete stream;
	}

---	if (_sfxQueue.size() > 0 && !_mixer->isSoundHandleActive(_sfxSoundHandle)) {
+++	if (_sfxQueue.size() > 0) {
		SoundInstance si = _sfxQueue.pop();

		Common::SeekableReadStream *stream = loadResourceToStream(soundRes, si.seg, "sound data");

Doors Not Opening

This one is simple. Certain doors (read: ActiveItems) in the map were non-interactable because the algorithm for accessing their instance was wrong. The original implemented a linked hash map for accessing the ActiveItems. In the process of refactoring, I forgot to include the “linked” part, which means we were accessing the wrong ActiveItem when they had the same hash key.

Fix: Restore the linked hash map design.

			           + ai->_data.instance.v + (ai->_data.instance.groupID << 2))
			          % ARRAYSIZE(instHash);

---			itemHash.setVal(hashVal, ai);
+++			ai->_nextHash = instHash[hashVal];
+++			instHash[hashVal] = ai;
		}
	}
}
	int16           hashVal = (((tp.u + tp.z) << 4) + tp.v + (group << 2))
	                          % ARRAYSIZE(instHash);


---	if (itemHash.contains(hashVal))
---		return itemHash.getVal(hashVal);
+++	for (ActiveItem *ai = instHash[hashVal]; ai; ai = ai->_nextHash) {
+++		if (ai->_data.instance.u == tp.u &&
+++		    ai->_data.instance.v == tp.v &&
+++		    ai->_data.instance.h == tp.z &&
+++		    ai->_data.instance.groupID == group)
+++			return ai;
+++	}

	return nullptr;
}

Crash After Saving

This one was difficult to debug because it crashed at places that seemed completely unrelated, and the ASAN report said the pointer that had tried to be accessed was wild. After some testing, though, I realized that it only occurred after saving the game. That meant it had to be something in the saving methods.
Turns out the cause was an array that I accidentally modified within saveActiveItemStates.

Fix: Use a local buffer for saving.

	//  Get a pointer to the current active item's state
	//  data in the archive buffer
---	statePtr = &stateArray[i][activeItem->_data.instance.stateIndex];
+++	statePtr = &bufferedStateArray[activeItem->_data.instance.stateIndex];

	//  Set the high bit of the state value based upon the
	//  active item's locked state

Bugs

BugSteps to reproduceExplanationSolved
Speech not finishing before dialog to buy item starts.Talk to any merchant and select an item fast.The entire line of dialogue is supposed to be spoken before the dialogue proceeds.Yes
Bands not loading correctly.Save and load the game when multiple bands are present.It crashes.Yes
Tooltip for armor status clips off-screen.It should shift off-center to make space for the text.No
Crash when using Staff of Life on non-PCs.When used on non-PCs, it should display the message “BreadStick only works on PC’s.”No
Crash when interacting with items in midair.Throw a spell scroll/potion and try learning/drinking it while it is midair.The crash also occurs on DOSBox. Possibly a bug in the original.No
Spell appearance not loading.Example: Fireball trap in the Goblin Cave. Minor Healing. Exception: FireballNo
ScummVM sucking up lots of ram over time.Walk around Bilton.Memory leak.Yes
The last character in a book’s text is missing.The last character on the last page is missing.No
Task Stack limit of 32 reached.Walk around places with many actors. (Example: Bilton; Castle Jovang)The original ignores task/task stacks that overflow. Expected behavior?No
Task limit of 64 reached.No

Speech Not Finishing Before Dialogue

The problem is that a single line of dialogue may consist of multiple voice lines. This is especially true in the case of merchant dialogue lines. Our implementation of stillDoingVoice only checked for the first voice line in a dialogue, so we fix that.

Fix: Allow stillDoingVoice to check for an array of voice lines.

bool stillDoingVoice(uint32 s[]);
...
bool stillDoingVoice(uint32 s[]) {
	uint32 *p = s;

	while (*p) {
		if (audio->saying(*p++))
			return true;
	}

	return false;
}
bool Speech::longEnough(void) {
	if (speechFlags & spHasVoice)
---		return (!stillDoingVoice(sampleID[0]));
+++		return (!stillDoingVoice(sampleID));
	else
		return (selectedButton != 0 || speechFinished.check());
}

Bands Not Loading Correctly

The problem is that when loading bands, we add them twice to the global BandList, which causes a crash later on.

Fix: Remove the line of code that adds the band to g_vm->_bandLists in Band::Band(InSaveFile *in).

		debugC(4, kDebugSaveload , "... id = %d", id);
	}

---	g_vm->_bandList->addBand(this);
}

Memory Leak

Over time my ScummVM was reaching 50% of RAM usage. This clearly indicated a resource leak somewhere. I spent some time thinking of ways to catch the memory leak before remembering ASAN reported the memory leaks when starting ScummVM without gdb.

Turns out the cause was in fetchPlatform, where I forgot to delete the stream I loaded.

Fix: Properly delete the resource in fetchPlatform.

	if ((stream = loadResourceToStream(tileRes, platformID + mapNum, "platform"))) {
		if (stream->skip(plIndex * sizeof(Platform))) {
			pce->pl.load(stream);
+++			delete stream;
			return &pce->pl;
		}
	}

Console Commands

I added some new console commands to ease testing:

// Teleport the three brothers to the center actor
bool cmdTeleportPartyHere(int argc, const char **argv);

// Plays a music track
bool cmdPlayMusic(int argc, const char **argv);

// PLays a voice track
bool cmdPlayVoice(int argc, const char **argv);

I’ve also extended the add_obj command to support specifying the number of items added.

bool Console::cmdAddObj(int argc, const char **argv) {
	if (argc == 2) {
		Actor *a = getCenterActor();
		a->placeObject(a->thisID(), atoi(argv[1]));
	} else if (argc == 3) {
		Actor *a = getCenterActor();
		int num = atoi(argv[2]);
		a->placeObject(a->thisID(), atoi(argv[1]), true, num);
	} else
		debugPrintf("Usage: %s <ObjectID> <num = 1>\n", argv[0]);

	return true;
}

Things are starting to get interesting. To be honest, I’d love to play this game completely by myself. Unfortunately, we’re tight on time, and this is playtesting, so I’m completely following the walkthrough. Big thanks to the uploader, Sinatar. Right now I’m near the end of his second session, which is 10 hours in. Let’s hope things remain a somewhat smooth ride until the end.


Leave a Reply

Your email address will not be published. Required fields are marked *