There are a number of steps that must be taken before you can add a new list box feeder to the source code. We will look at what it took to add a feeder that supplies a list of saved games that are used in the Single Player Tutorial. It is assumed you can modify and recompile the source and understand C programming.
In ui_local.h you must add variables to the uiInfo_t structure. This structure holds most of the information for menus and the lists that feeders supply to the list box are stored here. A feeder requires, at a minimum, the array that holds the list data, a variable that holds the element count of the array and a variable that is used to index the array. Since our saved games list is a list of file names we add the following to the uiInfo_t structure :
const char *saveList[MAX_SAVEGAMES];
int saveCount; // element count
int saveIndex; // array index
Earlier in ui_local.h we had defined the maximum number of saved games as :
#define MAX_SAVEGAMES 16
The next step is a little trickier. You must modify the menudef.h file in the source code and the same file in the baseq3/ui directory where your menu scripts are stored. In the source code this file is stored on the ui folder. This is not the code/ui folder where the ui source is but the ui folder at the same level as the code folder. If you look for the #define for the feeders you'll see that they define FEEDER_HEADS as 0 on up to FEEDER_CINEMATICS as 0x0F. We'll add the following to define our new feeder :
#define FEEDER_SAVE 0x10
Make sure this define is in both files or you'll run into problems.
Now we can start adding the feeder code. A feeder is referenced in three places so we must add our new code in each one of them.
In the UI_FeederCount function we must add code that will return the number of elements in our array, which we added to the uiInfo_t structure. Add the following to the function :
} else if (feederID == FEEDER_DEMOS) {
return uiInfo.demoCount;
} else if (feederID == FEEDER_SAVE) {
return uiInfo.saveCount;
}
return 0;
In the UI_FeederSelection function we add code to set the array index. This function receives the index of the element you selected in the list box and it sets the array index for the feeder. Add the following to the function :
} else if (feederID == FEEDER_DEMOS) {
uiInfo.demoIndex = index;
} else if (feederID == FEEDER_SAVE) {
uiInfo.saveIndex = index;
}
This index is used when you wish to transfer the array element to a cvar so it can be used by other parts of the menu. We'll see how this is done later.
In the UI_FeederItemText function we add code to return the data stored in the array at the index location. This function is where text feeders return the data for the desired element. Add the following to the function :
} else if (feederID == FEEDER_DEMOS) {
if (index >= 0 && index < uiInfo.demoCount) {
return uiInfo.demoList[index];
}
} else if (feederID == FEEDER_SAVE) {
if (index >= 0 && index < uiInfo.saveCount) {
return uiInfo.saveList[index];
}
}
You'll notice that we check the index provided to this function to make sure it falls within the correct bounds for our array.
This takes care of our feeder supplying information to the list box. Now we must add the code so we can prime the feeder with the information.
We'll add a function that will check the baseq3/save folder for files with the extension .sav and loads their names into our feeder array. It will also set the value for the element count variable as well. This function is the heart of the feeder code, as it determines what information the feeder will provide. In the ui_main.c file add the following :
static void UI_LoadSave( void ) {
char savelist[4096];
char saveExt[32];
char *savename;
int i, len;
Com_sprintf(saveExt, sizeof(saveExt), "sav");
uiInfo.saveCount = trap_FS_GetFileList( "save", saveExt, savelist, 4096 );
Com_sprintf(saveExt, sizeof(saveExt), ".sav");
if (uiInfo.saveCount) {
if (uiInfo.saveCount > MAX_SAVEGAMES) {
uiInfo.saveCount = MAX_SAVEGAMES;
}
savename = savelist;
for ( i = 0; i < uiInfo.saveCount; i++ ) {
len = strlen( savename );
if (!Q_stricmp(savename + len - strlen(saveExt), saveExt)) {
savename[len-strlen(saveExt)] = '\0';
}
Q_strupr(savename);
uiInfo.saveList[i] = String_Alloc(savename);
savename += len + 1;
}
}
}
You'll notice we make sure that we don't load too many file names and go out of our array bounds. We use the String_Alloc function to allocate space in our array, as space allocated this way is automatically freed when the program exits.
Next we create a uiScript command we can use to call this function and prime the feeder. In the UI_RunMenuScript function we add code to create this new command. Add the following to the function :
} else if (Q_stricmp(name, "LoadDemos") == 0) {
UI_LoadDemos();
} else if (Q_stricmp(name, "LoadSave") == 0) {
UI_LoadSave();
} else if (Q_stricmp(name, "LoadMovies") == 0) {
This will create the uiScript LoadSave command.
At this point we have added code that will allow us to prime the feeder and use the feeder in a list box to display the information. All that is left is too add a way to get the information from the list box and make it avalible to the rest of the menu. To do this we'll use cvars.
In the ui_main.c file there is a section dedicated to cvar definition. Search for } cvarTable_t; and the defintion section is right after that. In this section add the following :
vmCvar_t ui_realWarmUp;
vmCvar_t ui_serverStatusTimeOut;
vmCvar_t ui_SaveGame;
and in the next part, the definition of the cvarTable[] array add this :
{ &ui_serverStatusTimeOut, "ui_serverStatusTimeOut", "7000", CVAR_ARCHIVE},
{ &ui_SaveGame, "ui_SaveGame", "", CVAR_INIT},
This defines a cvar named ui_SaveGame that we can use to hold the information from the list box. To get the information into the cvar we must create another uiScript command. In the UI_RunMenuScript function we add code to create this new command. Add the following to the function :
} else if (Q_stricmp(name, "LoadDemos") == 0) {
UI_LoadDemos();
} else if (Q_stricmp(name, "LoadSave") == 0) {
UI_LoadSave();
} else if (Q_stricmp(name, "SaveGame") == 0) {
trap_Cvar_Set( "ui_SaveGame", uiInfo.saveList[uiInfo.saveIndex]);
} else if (Q_stricmp(name, "LoadMovies") == 0) {
This will create the uiScript SaveGame command.
We are now ready to use these new commands. In the menu where we are displaying our list of saved games, in the onOpen script, we run the uiScript LoadSave command to prime the feeder. The list box item would look something like this :
itemDef
{
name savelist
rect 190 171 370 200
type ITEM_TYPE_LISTBOX
style WINDOW_STYLE_SHADER
elementwidth 120
elementheight 20
textscale 0.25
elementtype LISTBOX_TEXT
feeder FEEDER_SAVE
textalign ITEM_ALIGN_LEFT
textaligny 14
border WINDOW_BORDER_FULL
bordercolor .5 .5 .5 .5
outlinecolor .5 .5 .5 .5
forecolor 1 1 1 1
backcolor 0 0 .5 .25
visible MENU_TRUE
doubleClick
{
uiScript SaveGame ;
}
}
When we doubleclick on an element of the list we run the doubleClick command, which uses our uiScript SaveGame command to copy the name of the file into the ui_SaveGame cvar. Now we can use that cvar elsewhere in the menu or the game.
This ends the tutorial on adding a new list box feeder. The code can be modified to create feeders that supply different information than what was used in the above but the principals will remain the same regardless.