Customizing a save title - FreeSlave/halflife-updated GitHub 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.
Credits
Thanks to a1batross for pointing out this trick.