The Beginning of the End (Progress Report)

Although this week had relatively weak progress due to me getting a vaccine shot, we still progressed in various fronts.

Saves

Let’s pick up from where we left off last time. Work on implementing the rest of the save system continued. We decided to try something new to count the number of bytes of each chunk – but first, a little refresher.

The basic structure of a SAGA2 save chunk.

Each chunk starts with an 8-byte header: the 4-byte chunk name, followed by a uint32 determining the size of the chunk data. Originally the saving program determined the necessary size through a series of sizeof() operations. However, due to alignment issues and platform differences this approach is not portable. I mentioned changing everything to constants, but that in and of itself is a dangerous process, because all of the bytes would have to be counted mostly by hand, and a mistake would be fatally easy to make.

Instead, what sev proposed to do was to to write the chunk data to an intermediate buffer and stream that to the OutSaveFile along with the chunk size. This way nothing has to be counted manually.

In order to achieve that, we used Common::MemoryWriteStreamDynamic and defined some macros:

#define CHUNK_BEGIN Common::MemoryWriteStreamDynamic *out = new Common::MemoryWriteStreamDynamic(DisposeAfterUse::YES)

#define CHUNK_END outS->writeUint32LE(out->size()); \
	outS->write(out->getData(), out->size()); \
	delete out

So when saving, we can simply do the following:

void saveGlobals(Common::OutSaveFile *outS) {
	outS->write("GLOB", 4);
	CHUNK_BEGIN;
	out->writeUint32LE(objectIndex);
	out->writeUint32LE(actorIndex);
	out->writeUint16LE(brotherBandingEnabled);
	out->writeUint16LE(centerActorIndicatorEnabled);
	out->writeUint16LE(interruptableMotionsPaused);
	out->writeUint16LE(objectStatesPaused);
	out->writeUint16LE(actorStatesPaused);
	out->writeUint16LE(actorTasksPaused);
	out->writeUint16LE(combatBehaviorEnabled);
	out->writeUint16LE(backgroundSimulationPaused);
	CHUNK_END;
}

In this example the macro doesn’t seem to do much good since the number of bytes here is easily countable, but it is very helpful in bigger saving steps, and it is also less fallible than a human counting.

Another development is that I learned booleans are saved as Uint16 in the original – go figure. After fixing that our saves ran on the original and vice-versa, hooray!1

And then finally after we finished work on getting the save/loading done, we implemented support for ScummVM’s extended savefile format, with the ability to see playtime and screenshots from the launcher. The process consisted of implementing Saga2Engine::saveGameState and using appendExtendedSave to write the extended save data2 (and then reading it back with readSavegameHeader).

Global Constructors

This week we also finished the rest of the global constructors. Not much to report, other than that some global constructors are quite sneaky.

static const StaticTilePoint NullTile = {(int16)minint16, (int16)minint16, (int16)minint16};

static auxAudioTheme aats[AUXTHEMES] = {
	{false, {NullTile, 0}, 0},
	{false, {NullTile, 0}, 0}
};

The above doesn’t generate errors when NullTile is defined within the same compilation unit, but it gives out a warning if it’s defined somewhere else. Go figure.

StaticWindow autoMapDecorations[numAutoMapPanels] = {
	{*(autoMapPanelRects[0]), NULL, autoMapTopPanelResID},
	{*(autoMapPanelRects[1]), NULL, autoMapMidPanelResID},
	{*(autoMapPanelRects[2]), NULL, autoMapBotPanelResID}
};

Dereferencing here also gives out a global constructor warning. I thought it was some kind of copy constructor thing going on, but I have no idea. To fix that I simply got rid of autoMapPanelRects and used the rects directly:

StaticWindow autoMapDecorations[numAutoMapPanels] = {
	{autoMapTopPanelRect, NULL, autoMapTopPanelResID},
	{autoMapMidPanelRect, NULL, autoMapMidPanelResID},
	{autoMapBotPanelRect, NULL, autoMapBotPanelResID}
};

To fix the other constructors I did the same steps as previously: either define it within Saga2Engine or relevant classes or transform it into a pointer and initialize it somewhere inside the file if it is static (or both).

Debug Console

This is an interesting one. ScummVM has support for a debug console, if you didn’t know. An example is implemented within Quux Engine, but I based myself off of SAGA Engine.

To implement it, you create a new file console.h and there create a class that inherits from GUI::Debugger:

class Console : public GUI::Debugger {
public:
	Console(Saga2Engine *vm);
	~Console() override;

private:
	Saga2Engine *_vm;

	bool cmdKillProtag(int argc, const char **argv);
...
	bool cmdGotoPlace(int argc, const char **argv);
};

And then you initialize it within Engine::run():

_console = new Console(this);
setDebugger(_console);

And then within console.cpp you implement the debug console commands:

Console::Console(Saga2Engine *vm) : GUI::Debugger() {
	_vm = vm;

	registerCmd("kill_protag", WRAP_METHOD(Console, cmdKillProtag));
...
	registerCmd("goto_place", WRAP_METHOD(Console, cmdGotoPlace));
}

Console::~Console() {
}

bool Console::cmdKillProtag(int argc, const char **argv) {
	debugPrintf("Killing protagonist\n");

	Actor *protag = (Actor *)GameObject::objectAddress(ActorBaseID);
	protag->getStats()->vitality = 0;

	return true;
}
...

It is really quite the interesting tool to use and develop commands for. Not only that, we plan on using it to playtest and determine easily if the game is completable. We’re developing. Ain’t got time for playing here!

Command usefulness factor? It is easily verifiable.

[1] Remember me talking about enums? Weird thing is I didn’t have to change their size in order for it to work. Oh well.

[2] We actually had to change appendExtendedSave and add the method appendExtendedSaveToStream in order for it to work with WriteStreams.


Leave a Reply

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