Created By:

Andrew Griffin

eMail:

andrew_griffin@bigfoot.com

Difficulty Scale:

Medium



This tutorial introduces the use of .ini files and shows how they can be used with a
relatively simple function to produce results. One use for .ini files might be in 
balancing new weapons - instead of having to recompile the .dll for each change, you 
could place the damage the weapon does in an .ini file. Then you can simply edit 
that .ini file, type /disconnect, and then start a level, and the change will take 
effect, saving you a lot of time. Of course, this is just the simplest example of 
what an .ini file can be used for, but you get the idea.
 
Now, first of all you will need some code to use .ini files. Lucky you! I'm 
providing it to you with this tutorial. It is a straight port from a C++ class I 
wrote some time ago, but is easy to use. The code examples will show you how to 
import the .ini file code into your Quake 2 project.
 
Ok, so what are we going to do in this tutorial? We are going to write a single 
function that will use an .ini file to allow the easy modification of a monster 
type's health. So you could, for example, change the monster_soldier_light to have 
150 health, or whatever you feel like.
 
But first, we need to put in the .ini file handling. You'll need to download the 
file containing the code for the .ini file routines, so I'm assuming you have it. 
Put both the ini.c and ini.h files into your project as you normally would. Then you 
need to make a change to the g_local.h file. Add the following lines (near the 
bottom is easiest):
 
#include "ini.h"
extern IniFile ini_file;
 
We add these in the g_local.h file because then all of the other files in the
project have access to them. We use a global variable for the .ini file for the same 
reason.
 
Now you need to define the variable ini_file somewhere. It is probably easiest to do 
this in g_main.c as that is where a lot of the other global variables are located. 
 
Add the following line to g_main.c at about line 46 or so:
 
IniFile ini_file = {0,0,0,0,0,NULL,NULL,NULL,NULL};
 
This not only defines the variable ini_file, but also initialises it, just in case :)
 
You now have an IniFile structure that is visible to the rest of the project. Now 
you need to load the .ini file. You only want to do this once, so the best place is 
in the InitGame function in g_save.c. This function gets called only when the .dll 
is loaded, so that is the perfect time to load in the .ini file.
 
We are going to use the file 'health.ini' in this example, so the line to add in 
InitGame would be:
 
i = Ini_ReadIniFile("health.ini", &ini_file);
 
Add this line just after the first gi.dprintf(). You want to check 'i' to make sure 
that the .ini file was read in correctly. A value of 1 means everything went OK. A 
value of 0 meant something stuffed up, and you can't use ini_file.
 
The 'health.ini' file should go in the same directory as the quake2.exe file. If you 
want to put it into a subdirectory (where your modified .dll file resides, for
example), then you would need to change the above line to (assume the subdirectory 
was called my_mod):
 
i = Ini_ReadIniFile("my_mod/health.ini", &ini_file);
 
That is all there is to reading in the .ini file. Now we are going to add the 
function that uses it.
 
Create a new C file and call it m_health.c. Add it to your project. Add the
following line to the g_local.h file:
 
void ModifyHealth(edict_t *ent);
 
This is the function that m_health.c will contain. Adding it in g_local.h means that 
the other parts of the project will be able to see it.
 
The m_health.c file will be:
 
#include "g_local.h"
//////////////////////////////////////////////
//-------------
// ModifyHealth
//-------------
// A very simple function that looks into the health.ini file and tries 
// to match the entity's classname with an entry in the [Monsters - Health] 
// section. If it finds such an entry, it changes the health of that 
// monster to the specified value.
//////////////////////////////////////////////
void ModifyHealth(edict_t *ent)
{
        char *p;
 
        // first we make sure that the .ini file is open
        if (ini_file.ini_file_read)
        {
                // now we use ent's classname to try and
                // find the appropriate entry in the health.ini
                // file - now you know why I used those particular
                // names in the health.ini file.
                p = Ini_GetValue(&ini_file, "Monsters - Health", ent->classname);
                // check that what was returned wasn't NULL
                if (p != NULL)
                        ent->health = atoi(p);
        }
}
 
This is a very basic function. What it does is use the entity's classname to look up 
a value in the health.ini file. If that lookup failed, nothing happens. If the
lookup worked, the entity's health is changed.
 
You get values (in the form of 'strings') by using the Ini_GetValue function. The 
format of this function is: the IniFile, the .ini file section header, the entry in 
that section. Ini_GetValue returns NULL if either the section name was invalid for 
that .ini file, or the entry couldn't be found.
 
That is all you need to do! Then, for each monster you want to be affected by this 
.ini file, you add a single line to their spawn function (naturally, AFTER they have 
their health set). This line would look like
 
ModifyHealth(self);
 
for most spawn functions - just make sure to change 'self' to the appropriate 
edict_t name if you come across a monster spawn function that does not use 'self' 
(most do). For the 3 soldier monsters, do not put ModifyHealth() in
monster_soldier_x, but rather in the three individual spawn function (as this is 
where the health is set).
 
For example, the m_hover.c file looks like:
        VectorSet (self->maxs, 24, 24, 32);
        self->health = 240;
        self->gib_health = -100;
        self->mass = 150;
        self->pain = hover_pain;
You would change this to:
        VectorSet (self->maxs, 24, 24, 32);
        self->health = 240;
        self->gib_health = -100;
        self->mass = 150;
        ModifyHealth(self);
        self->pain = hover_pain;
 
and it would then attempt to modify the health of the hover monster, but nothing bad 
will happen if that monster isn't listed in the .ini file.
 
There is one final change that you need to make. Because the .ini file handler 
allocates its own memory, you have to make sure to free that memory when the .dll is 
unloaded. If you don't do this, this memory will not be freed, giving you a small 
memory leak. To free the memory, you make a call to Ini_FreeIniFile. This call
should be made in the ShutdownGame() function in g_main.c:
 
void ShutdownGame (void)
{
        gi.dprintf ("==== ShutdownGame ====\n");
 
        // MODIFIED
        // free up memory associated with the ini file
        Ini_FreeIniFile(&ini_file);
        // END MODIFICATION
 
 
        gi.FreeTags (TAG_LEVEL);
        gi.FreeTags (TAG_GAME);
}
 
When the .dll is unloaded (through a /disconnect call or something else), the memory 
that was allocated will be freed, so no memory leak.
 
That's it for this tutorial.
 
There should be two small downloads for this tutorial: one containing the code for 
the .ini file handling, and one containing the health.ini file.
 
 
ini.zip, file handling
health.zip, health.ini