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