DOCUMENTATION FOR DATFILES MODULE
Part 1: Reading Markers from mission.dat files, with Examples

By Stumpy

A bit of mod work has reminded me that scripters often run into the problem of determining objects, positionals, sounds, and other "markers" that have been defined in the mission.dat file by using FFEdit Mission tab. For example, how do I get a list of all the positionals in a mission? What about all the objects that aren't dynamic or don't follow the '_impobj_#' naming convention? Where's the nearest streetlight so I can wield it? What if I want to write an attribute that increases a character's energy or health whenever he's near the appropriate Energy X canister?

Of course, for some of these tasks, it is possible to go back into FFEdit and manually copy down the names of the objects, positionals, and so on that are of interest, but that's a bit of a pain.

So, I turned codeward and wrote a few functions that extract that information from the mission.dat file itself and make it available for scripting during the mission. These also work with the base.dat files for use in the base scripts.

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

What follows are some quicky descriptions of the functions, a couple examples, and then a few slightly more technical notes on use.

I really hope this will be valuable to scripters/modders out there. Let me know if you have trouble with the code or if this doc needs clarification.

 

Starting Marker Functions

Notes On Use

In all of the following, not specifying MissionFileName will cause the function to try and read the current mission.dat or base.dat. Generally, this means the functions can be called without any arguments. However, the game must be run with the -log option for this to work, as it would be if FFX is enabled, for instance.

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 the mission markers, if desired. In that case, MissionFileName must be specified.

 

General Functions

These first seven functions return lists of markers of various types. I think these include all the marker types one can specify in FFEdit. The markers returned are those defined in the mission's mission.dat (or base.dat for base missions), which are essentially all objects present at the start of the mission, before any are generated via scripting.

Mission_GetStartingObjects(MissionFileName='')

Returns a list of all starting objects, both static and dynamic. These includes vehicles, buildings, characters, et cetera.

Mission_GetStartingLights(MissionFileName='')

Returns a list of all light markers.

Mission_GetStartingSounds(MissionFileName='')

Returns a list of all sound markers.

Mission_GetStartingPositionals(MissionFileName='')

Returns a list of all positionals.

Mission_GetStartingRoadNodes(MissionFileName='')

Returns a list of all road node markers.

Mission_GetStartingCivNodes(MissionFileName='')

Returns a list of all civ node markers.

Mission_GetStartingTraffics(MissionFileName='')

Returns a list of all traffic markers.

Mission_GetMissionName(MissionFileName='')

Returns a string with the mission name in it. This is the "Name" field in the Missions page in FFEdit.

Mission_GetTextureDir(MissionFileName='')

Returns a string with the mission texture directory in it. This is the "Texture Dir" field in the Missions page in FFEdit.

Mission_GetLayoutFile(MissionFileName='')

Returns a string with the mission layout file name in it. This is the "Layout File" field in the Missions page in FFEdit.

Mission_GetMapExtents(MissionFileName='')

Returns a 6-tuple with the coordinate limits of the map in it. The tuple looks like (Xmax, Ymax, Zmax, Xmin, Ymin, Zmin).

 

Lower-Level Function

This is the main function, although it is primarily intended for internal use.

Mission_ReadStartingMarkers(MissionFileName='', ForceRead=0, verbose=0)

It returns a dictionary of marker:markerType pairs. For markers that are objects (that is physical things like buildings, cars, or characters), the pair will be of the form marker:templateName. For other markers, markerType is the integer value used in the DAT file to represent a particular kind of marker. (See datfiles.py for these values.)

MissionFileName is the path and filename of the mission or base DAT file to be searched for markers. 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 causes a line with each marker's info to be printed as it is read from the DAT file.

 

Constants

Some constants are defined in the module. For the most part, scripters won't have any use for these marker type constants. They support the higher-level functions.

MT_GENERIC, MT_CHARACTER, MT_LIGHT, MT_SOUND, MT_POSITIONAL, MT_ROAD_NODE, MT_CIV_NODE, and MT_TRAFFIC.

For debugging purposes, a dictionary with those values as keys and their string names as values is defined as MT_MARKER_TYPES. That is MT_MARKER_TYPES[MT_POSITIONAL] will have the value 'MT_POSITIONAL

 

Examples

Example 1, Seeing Positionals

First, something very straightforward. To show all the positional markers in the current mission, we might try

Code:
>>> import datfiles
>>> print datfiles.Mission_GetStartingPositionals()

For instance, in the first mission of the main campaign, we would see something like

Code:
['thugb1_prox', 'thugb5_patrol_1', 'thugb5_patrol_2', 'mug', 'oconnor_spawn', 'squad_2', 'squad_1', ...]

and there would be 17 total positionals.

(Of course, this is assuming we have unzipped mission.dat from missions.ff into Data/Missions/01_begin/. See the first note below.)

Example 2, Getting All Objects

This next example is slightly more involved but pretty useful for scripting: It returns just about all of the objects that exist in the current mission. It does that by using Mission_GetStartingObjects() to get all the objects that were in the mission.dat file and then grabbing all of the 'mobj_#' objects that were created by the engine during game play. To these it adds any dynamic objects spawned during the mission.

Code:
def Mission_GetMostObjects():
    try:
        possobjs = filter(Object_Exists,datfiles.Mission_GetStartingObjects())
    except:
        possobjs = filter(Object_Exists,map(lambda i: '_impobj_%d' % i, range(1000)))
    mobjs = filter(Object_Exists,map(lambda i: 'mobj_%d' % i, range(1000)))
    possobjs.extend(mobjs)
    objects = possobjs
    for obj in Mission_GetDynamicObjects():
        if not obj in possobjs:
            objects.append(obj)
    return objects 

This function should return nearly all objects present in a level. This newer function will get all the starting objects (defined by FFEdit in mission.dat), including those that have names that are not of the form '_impobj_#', which the previous version would miss. That is, mission.dat objects with names like 'powerupcp_1' or 'building_police_station' would have been missed by the older function, but will be found with this one.

It will still miss static objects spawned via script that do not follow the 'mobj_#' naming convention that the engine uses. (To have the engine do the naming, use Object_SpawnAt() to do your spawning and leave out the name argument.)

 

Notes

First, for any of these functions to work, there must actually be a mission.dat (or base.dat) file in the mission folder for whatever mission is running. I think that's usually true (at least in all the mods I've seen), but keep in mind that it is possible to distribute a mod with these files compressed (zipped) into .ff files that the engine decompresses internally (I assume) without putting the DAT file anywhere nice. That's what the main campaign does and you have to unzip the missions.ff file manually if you want to experiment with those missions using these functions.

Also, by calling these functions with no MissionFileName specified (or by specifying an empty string), Mission_ReadStartingMarkers() must parse the ff.log file, which is why your starting shortcut must have the -log option in it. This is only done the first time any of these functions is called during a mission, to minimize I/O delays. It is slightly faster to explicitly give the MissionFileName, but I doubt the speed increase will be noticeable.

I wrote this module with an eye on keeping delays minimal. Because of that, the functions try to avoid re-reading the mission.dat file every time one of they are called and that should keep disk I/O pretty low. The first read, however, isn't necessarily quick, although I had no trouble reading some pretty healthy mission.dats with over 1300 markers in them and the function call was finished before I could blink.

Finally, I notice that the mission.dat files can have lots of garbage in them, particularly after using FFEdit to delete, add, and rename markers. I don't just mean that I don't understand all of the header info in the files (which I do not), but also that there appear to be entries written over other entries that were not completely erased and so on. (There are even parts of some mission.dat files that are clearly python code segments; don't ask me how it got in there.) I only mention this because I think that my parser is using the right tricks to ignore most of the junk that I saw and still get to the useful data, but I can't guarantee that something in someone's mission.dat won't screw things up. If you have a working DAT file with which these functions do not work, PM me and I will check into it.