DOCUMENTATION FOR DATFILES MODULE
Part 3: Reading Object Templates from objects.dat files, with Examples

By Stumpy

Sometimes we want to know information about an object as it is defined on the Templates tab in FFEdit. What is the object's base mass (before material multipliers, for characters)? What is its minForce template attribute?  What is its NIF file?   

NOTE: For the most part, during a mission, this information is available through in-game functions like Object_GetClass() and Object_GetAttr(). But those do require that an item of the appropriate template is already present in the mission. When we want to check without spawning, reading from objects.dat is made possible by the Campaign_ReadObjects() function. This can be useful when you have to get this information outside the game, but it can also be needed occasionally when trying to determine a character object's real template in a skirmish match (for which the Object_GetTemplate() function returns unhelpful results like 'custom_template_178'), perhaps by matching it with their character attributes or hero files and some other unique identifier (like a 'complex' attribute set by FFX Control Centre).

In addition, there are some miscellaneous functions in the module that serve utility purposes, such as cleaning up the objects.dat file.

The module file datfiles.py can go in the Scripts directory. Any python file that uses these functions should import datfiles or from datfiles import *.

 

Reading objects.dat

Notes On Use

For calls to Campaign_ReadObjects() made in the game, not specifying FileName will cause the function to try and read the objects.dat file for the current mod. Generally, this means the function can be called without any arguments.

None of these functions in the datfiles module use the ff, js, cshelper or other Freedom Force-specific modules, so they can be run from a python shell outside the game to summarize or explore DAT file entries, if desired. In that case, FileName must be specified.

The Main Function

The principle function is Campaign_ReadObjects() which reads character information from the objects.dat file and returns it in a dictionary object.

Campaign_ReadObjects(FileName='',ForceRead=0,verbose=0)

Returns a dictionary whose keys are the template names of all templates in the objects.dat file. The value for each entry is itself a dictionary whose keys are the FFEdit data fields for that template.

FileName is the path and filename of the objects.dat file to be read. As noted above, it can usually be left blank when used during a running mission.

ForceRead forces the DAT file to be re-read, even if it has been read before.

verbose prints some info as each object is read from the DAT file.

The Dictionary

Campaign_ReadObjects() returns a dictionary. See the Python Tutorial for a quick summary of Python dictionaries and the Python Library entry for a more complete description, with more of the methods available for using dictionary data, keeping in mind that Freedom Force uses Python 1.5.

Each object in the "objects" tab of FFEdit will be a key in the dictionary. For example, in the main campaign, 'alchemiss' will be a key and 'building_boathouse' will be a key. The value corresponding to each key is itself a dictionary of data fields read for that character. The following are some of the keys and values for that dictionary. For most of them, see the FFEdit documentation.

'NIF' : NIF file for the template, a string
'class' : the game object class, a string (These correspond to the OC_* constants mentioned in the scripting documentation. Note that villain in 'GAME_OBJ_VILLIAN' really is misspelled, as is the corresponding constant OC_VILLIAN. Also, see this thread for a note on the constants corresponding to 'GAME_OBJ_GENERIC' and 'GAME_OBJ_POWER_PROXY'.)

Almost all templates have the above fields. Most, but not all, have the following:

'material' : material, floating point number with integer values from 0 to 10 (See, for instance, the Materials and Resistances page on Alex's Freedom Fortress for what these mean.)
'mass' : mass in kg, floating point number (Note that characters in the game will have their mass multiplied by some constant based on the material.)

See the FFEdit documentation for other template attributes such as 'minForce', 'throwable', etc. which all have floating point values. Those template attributes as well as the other optional fields such as 'skin', 'explosion', 'arg2', 'arg3', and 'arg4' are only in the dictionary if they have been given values in FFEdit.

Utility Functions

Several utility functions serve to modify the objects.dat file in ways that can be useful. Though they are very specific, they are documented here in case they help in mod-making tasks.

Campaign_ChangeObjectMinforce(FileName='',MassFactor=10.0,verbose=0)

By default, the game sets the minForce template attribute for most non-character objects to a very small value. The result is oven that small forces on even very heavy held objects (those picked up or wielded) will cause the holding character to drop the object.

This function reads an objects.dat file and changes the minForce values of all the generic objects (that don't already have it explicitly set) to be a specifyable multiple of the object's mass.

The function makes a backup of the altered objects.dat file as FileName+'.bak' (or FileName+'.bak2' and so on if that file already exists) in the same directory that the file was in.

FileName is the path and filename of the objects.dat file to be modified. As usual, it can usually be left blank when the function is called within the game and it will find the right DAT file for the current mod.

MassFactor the multiple by which the mass will be multiplied to obtain the minForce value. Some testing shows 10 to be a workable value.

verbose prints some info as each object is modified.

Campaign_RemoveObjectMaxEP(FileName='',verbose=0)

For certain characters in the first game, it was useful to set their 'maxEnergyPoints' template attribute to a value lower than 100 EP. This is very rarely true in the second game and having the lower value can prevent the character from ever using certain of his powers if they require 100 EP ("High" energy cost) to use.

This function reads an objects.dat file and removes the 'maxEnergyPoints' template attribute from any character templates for whom it has been set to something elss than 100.

The function makes a backup of the altered objects.dat file as FileName+'.bak' (or FileName+'.bak2' and so on if that file already exists) in the same directory that the file was in.

FileName is the path and filename of the objects.dat file to be checked and modified. As usual, it can usually be left blank when the function is called within the game and it will find the right DAT file for the current mod.

verbose prints some info as each object is modified.

Campaign_SetMissingComplexAttribs(ObjectFileName='',CharacterFileName='',verbose=1)

The 'complex' template attribute must be uniquely set for each character for the chardata module (on which, for example, M25's AI and many FFX attributes rely) to correctly identify the characters.

Normally, the 'complex' attribute is set for characters by the FFX Control Centre but the current version of the Control Centre does not set the attribute for characters of all character classes, such as for the civilian and tank classes.

This function reads the objects DAT and a characters DAT file and checks if character in the latter file that has an object template in the former file is missing its 'complex' attribute. If it finds characters like that, it tries to assign them unique 'complex' values.

The function makes a backup of the altered objects.dat file as FileName+'.bak' (or FileName+'.bak2' and so on if that file already exists) in the same directory that the file was in.

ObjectFileName is the path and filename of the objects.dat file to be checked and modified. As usual, it can usually be left blank when the function is called within the game and it will find the right DAT file for the current mod.

CharacterFileName is the path and filename of the characters.dat file to be read. Similarly, it can usually be left blank when the function is called within the game and it will find the right DAT file for the current mod.

verbose prints some info as each object is modified.

Campaign_RemoveObjectAttrib(attribute,ObjectFileName='',CharactersOnly=1,verbose=1)

There are times when one needs to remove a particular attribute from all game objects and it can be a very tedious process to do that for each object one at a time using FFEdit. The particular case that prompted this function was one where many instances somehow multiple 'complex' attributes (with different values) were being assigned to single objects in the templates page of FFEdit, causing problems. It turned out that the easiest thing to do was to remove the attribute from all of the objects and then use the FFX Control Centre to re-assign them.

This function reads an objects.dat file remove from each object in it the named attribute.

The function makes a backup of the altered objects.dat file as FileName+'.bak' (or FileName+'.bak2' and so on if that file already exists) in the same directory that the file was in.

ObjectFileName is the path and filename of the objects.dat file to be checked and modified. As usual, it can usually be left blank when the function is called within the game and it will find the right DAT file for the current mod.

CharactersOnly change only objects that belong to one of the character classes. Those classes are GAME_OBJ_MINION, GAME_OBJ_HERO, GAME_OBJ_POLICE, GAME_OBJ_CIVILIAN, GAME_OBJ_VILLIAN, and GAME_OBJ_TANK. By default, this option is set.

verbose prints some info as each object is modified.

UpdateObjectsFromDAT(source, target, items)

Take two object DAT files (a source and a target) and a list of items to change and this function will find those items in each DAT and then copy the items from source into target and save the new target. BACK UP TARGET BEFORE DOING THIS!

 

Examples

Example 1, A Typical Template Entry

First, something very straightforward. To show all the show the typical dictionary returned by this function, we might look at the main campaign entry for one of the typical minions.

Code:
>>> import datfiles 
>>> thug = datfiles.Campaign_ReadObjects()['oc_thug_bat']

This will return something like

Code:
{'class': 'GAME_OBJ_MINION', 'prestige': 5.0, 'skin': 'random', 'mass': 80.0, 'complex': 144.0, 'maxEnergyPoints': 0.0, 'elasticity': 0.0, 'NIF': 'library\\characters\\thug2\\character.nif', 'material': 0.0, 'templateName': 'oc_thug_bat', 'pickupDistance': 2.0, 'scary': 0.0}

Note that the keys of a python dictionary are not usually sorted.

(Of course, this is assuming we have unzipped objects.dat from data.ff into the Freedom Force\Data directory.)

Example 2, Cold Conjuring

Say we are writing some code for a character who can create objects made out of ice, we might like something like the following:

Code:
import datfiles 
	def GetIceTemplates():
    temps = datfiles.Campaign_ReadObjects()
    iceobjs = []
    for temp in temps.keys():
        # check that it's made of ice
        if ( 'material' in temps[temp].keys() ) and ( temps[temp]['material'] == 9 ) and \
           # and that it's an object (not a character)
           ( temps[temp]['class'] == 'GAME_OBJ_GENERIC' ) and \
           # and that it has mass (e.g. it's not a portal or something)
           ( 'mass' in temps[temp].keys() ) and ( temps[temp]['mass'] > 0.0 ) and \
           ( ( 'physical' not in temps[temp].keys() ) or \
             ( 'physical' in temps[temp].keys() ) and ( temps[temp]['physical'] == 1.0 ) ):
            iceobjs.append(temp)
    return iceobjs

This will print something like

Code:
['snowball', 'ffx_elem_ice', 'gate_frozen', 'ffx_icewall', 'crystals']

 

Notes

First, for this function to work, there must actually be an objects.dat file, either in the mod folder for whatever mission is running or where specified by FileName.

I wrote this module with an eye on keeping delays minimal. Because of that, Campaign_ReadObjects() tries to avoid re-reading the objects.dat file every time it is called and that should keep disk I/O pretty low. The first read, however, isn't necessarily quick. On my laptop machine, it read the 1.7MB main campaign objects.dat (the first time) in about 0.35 seconds. This can be a noticeable jump in the game. Some mods have pretty big objects.dat files, so this call can take over a second. Subsequent calls are much faster (about a millisecond). If this function is to be called during a mission, it is a good idea to put a call to Campaign_ReadObjects() (the result doesn't need to be assigned to a variable for the caching to work) in the onPostInit() after the cut-scene, so that the file is read in the background.

Finally, I didn't fully decode every byte of the DAT files. There was certain information that I wanted to extract and I tried to ignore the rest. However, that means that there may be parts of a DAT file that confuse the reader functions, though I have tested each of these functions at least on the main campaign DATs. If you have a working DAT file (everything looks fine in FFEdit) with which these functions do not work, PM me and I will check into it.