Customizing a save title - FreeSlave/halflife-updated Wiki

The problem

If you save game on custom Half-Life map and then press 'Load Game' you'll see that the save title is the same as the name of the map. However saving in the original Half-Life campaign produces a save with a title of the chapter. It's because the engine knows about relation between each map and each chapter. So it's in the engine, we're doomed! Not exactly. This behavior can be redefined in the game library.

The solution

The solution is to export our own SV_SaveGameComment function from the server library to the engine. We'll take the engine function from Xash3D and port it to the Half-Life SDK with minimal modifications.

It does not really matter in which .cpp file of the server library to put the implementation, in this tutorial I've chosen world.cpp. You may want to do the same or even create a new .cpp file. Add the following lines in the file of your choice.

struct TitleCommet
{
	const char *mapname;
	const char *titlename;
};

TitleCommet gTitleComments[] =
{
	// default Half-Life map titles
	// ordering is important
	// strings hw.so| grep T0A0TITLE -B 50 -A 150
	{ "T0A0", "#T0A0TITLE" },
	{ "C0A0", "#C0A0TITLE" },
	{ "C1A0", "#C0A1TITLE" },
	{ "C1A1", "#C1A1TITLE" },
	{ "C1A2", "#C1A2TITLE" },
	{ "C1A3", "#C1A3TITLE" },
	{ "C1A4", "#C1A4TITLE" },
	{ "C2A1", "#C2A1TITLE" },
	{ "C2A2", "#C2A2TITLE" },
	{ "C2A3", "#C2A3TITLE" },
	{ "C2A4D", "#C2A4TITLE2" },
	{ "C2A4E", "#C2A4TITLE2" },
	{ "C2A4F", "#C2A4TITLE2" },
	{ "C2A4G", "#C2A4TITLE2" },
	{ "C2A4", "#C2A4TITLE1" },
	{ "C2A5", "#C2A5TITLE" },
	{ "C3A1", "#C3A1TITLE" },
	{ "C3A2", "#C3A2TITLE" },
	{ "C4A1A", "#C4A1ATITLE" },
	{ "C4A1B", "#C4A1ATITLE" },
	{ "C4A1C", "#C4A1ATITLE" },
	{ "C4A1D", "#C4A1ATITLE" },
	{ "C4A1E", "#C4A1ATITLE" },
	{ "C4A1", "#C4A1TITLE" },
	{ "C4A2", "#C4A2TITLE" },
	{ "C4A3", "#C4A3TITLE" },
	{ "C5A1", "#C5TITLE" },
	{ "OFBOOT", "#OF_BOOT0TITLE" },
	{ "OF0A", "#OF1A1TITLE" },
	{ "OF1A1", "#OF1A3TITLE" },
	{ "OF1A2", "#OF1A3TITLE" },
	{ "OF1A3", "#OF1A3TITLE" },
	{ "OF1A4", "#OF1A3TITLE" },
	{ "OF1A", "#OF1A5TITLE" },
	{ "OF2A1", "#OF2A1TITLE" },
	{ "OF2A2", "#OF2A1TITLE" },
	{ "OF2A3", "#OF2A1TITLE" },
	{ "OF2A", "#OF2A4TITLE" },
	{ "OF3A1", "#OF3A1TITLE" },
	{ "OF3A2", "#OF3A1TITLE" },
	{ "OF3A", "#OF3A3TITLE" },
	{ "OF4A1", "#OF4A1TITLE" },
	{ "OF4A2", "#OF4A1TITLE" },
	{ "OF4A3", "#OF4A1TITLE" },
	{ "OF4A", "#OF4A4TITLE" },
	{ "OF5A", "#OF5A1TITLE" },
	{ "OF6A1", "#OF6A1TITLE" },
	{ "OF6A2", "#OF6A1TITLE" },
	{ "OF6A3", "#OF6A1TITLE" },
	{ "OF6A4b", "#OF6A4TITLE" },
	{ "OF6A4", "#OF6A4TITLE" },
	{ "OF6A5", "#OF6A4TITLE" },
	{ "OF6A", "#OF6A4TITLE" },
	{ "OF7A", "#OF7A0TITLE" },
	{ "ba_tram", "#BA_TRAMTITLE" },
	{ "ba_security", "#BA_SECURITYTITLE" },
	{ "ba_main", "#BA_SECURITYTITLE" },
	{ "ba_elevator", "#BA_SECURITYTITLE" },
	{ "ba_canal", "#BA_CANALSTITLE" },
	{ "ba_yard", "#BA_YARDTITLE" },
	{ "ba_xen", "#BA_XENTITLE" },
	{ "ba_hazard", "#BA_HAZARD" },
	{ "ba_power", "#BA_POWERTITLE" },
	{ "ba_teleport1", "#BA_POWERTITLE" },
	{ "ba_teleport", "#BA_TELEPORTTITLE" },
	{ "ba_outro", "#BA_OUTRO" },
};

/*
=============
SV_SaveGameComment
Exporting this allows to replace the engine function.
=============
*/
extern "C" EXPORT void SV_SaveGameComment( char *text, int maxlength )
{
	const char *pName = NULL;

	text[0] = '\0'; // clear

	unsigned long i;
	const char *mapname = STRING( gpGlobals->mapname );

	for( i = 0; i < ARRAYSIZE( gTitleComments ); i++ )
	{
		// compare if strings are equal at beginning
		int len = strlen( gTitleComments[i].mapname );
		if( !strnicmp( mapname, gTitleComments[i].mapname, len ))
		{
			pName = gTitleComments[i].titlename;
			break;
		}
	}

	if( !pName )
	{
		entvars_t *pevWorld = VARS( INDEXENT( 0 ) );

		if( pevWorld->message != 0 )
		{
			// trying to extract message from the world
			pName = STRING( pevWorld->message );
		}
		else
		{
			// or use mapname
			pName = STRING( gpGlobals->mapname );
		}
	}

	snprintf( text, maxlength, "%-64.64s %02d:%02d", pName, (int)(gpGlobals->time / 60.0 ), (int)fmod( gpGlobals->time, 60.0 ));
}

This function replicates the knowledge the engine has about Half-Life, Opposing Force and Blue Shift map names and chapters. But it has an additional feature. If the map name is not one of original campaigns, the save file will have Map Description as a title (the message property of the worldspawn). As a mapper you edit it in the Map properties. If it's enough in your case, you can stop here.

Note: Xash3D already implements such behavior. You don't need to apply any changes to your game library code if your mod is going to be Xash3D only.

If you don't want to rely on the Map Description / Title, the second option is to modify the relation list of the original maps and chapters (gTitleComments array). Append your map and chapters names to the list. It's enough to include just prefixes, e.g. if all maps from the same chapter share the same prefix, then it's enough to add

	{ "chapter1_", "Chapter One" },

This way saves that were made on the maps chapter1_map1, chapter1_map2, etc, will have the title Chapter One.

Note that the order in the list is important: the first found entry will be used to choose the save title. So if you have maps map1 and map10 the chapter title for map1 will be used as the chapter title for map10 as they the share the same prefix.

Patch

Credits

Thanks to a1batross for pointing out this trick.