DOCUMENTATION FOR DATFILES MODULE
Part 5: Reading ‘Filled-In’ Characters from Saved Games, Hero Files, And DAT files; with Examples

By Stumpy

We often like to know what a character's attributes and powers are and these are often not available in the game. In addition, we want to know what levels the powers are at, how many attributes the character has activated (purchased) and so on. We've already gotten the plain FFEdit character information from Campaign_ReadCharacters() - here we note the functions that fill out that data and also read data from saved games and hero files.

These functions are pretty much the heart of the data reading utility. When combined with GetCharacterData() in the chardata module, we have all we need to determine a built-in (created with FFEdit or EZHero using "Send to DAT") character's information during a campaign. If we combine that with the unique complex attributes automatically set for us when we scan a given mod folder with DrMike2000's FFX Control Centre, we can get the same information for characters in Danger Room skirmishes.

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 *. For example, we can say

Code:
import datfiles 
myhero = datfiles.Campaign_ReadCharacterData('hero_1')

or we can say

Code:
from datfiles import * 
myhero = Campaign_ReadCharacterData('hero_1')

 

Reading Filled-In Characters

Notes On Use

For calls to any of these function made during the game, not specifying FileName will cause the function to try and read the appropriate files for the current mod, when it can figure them out. Generally, this means most function can be called without any Filename arguments. However, hero files must still be specified, although the function will try looking in the hero directory if only the hero file name itself (and not the full path) is specified for the FileName.

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 Functions

A "filled-in" character is one for which the data dictionary for that character is filled-in with information such as the full power dictionary for each of the character's powers, the levels at which the powers are bought, and the number of active attributes the character has purchased. This information is all already in a hero file (although it can change if the hero is recruited into a campaign and spends CP on new powers or attributes or raising the level of existing powers). The information is generally in even an built-in character's entry in a saved game file, but the power names are just names, not full power dictionaries. Finally, barest of all, are unrecruited built-in heroes, who need powers filled in as well as power levels set to their default starting levels.

There are three principle functions for reading filled-in characters, Campaign_ReadCharacterData(), Campaign_ReadCharactersFromSavedGame(), and Campaign_ReadHeroFile().

Campaign_ReadCharacterData(ModPath='', ForceRead=0, verbose=0)

This function reads in the characters.dat, objects.dat, and powers.dat for a mod and returns a dictionary of filled-in dictionaries, one each character for all of the built-in characters in the mod. The outer dictionary's keys are the characters' template names. For each template, its value is a diction with almost all of the fields from the character's Characters tab and Objects tab FFEdit entries as well as his powers from the FFEdit Powers tab. So, if we call chars = Campaign_ReadCharacterData() then chars['mentor'] is the whole filled-in dictionary for Mentor, chars['mentor']['speed'] is his speed, chars['mentor']['powers'] is a dictionary where each entry is one of his powers, chars['mentor']['mass'] is his mass (this is from the template data, so it does not account for material multipliers), and so on.

This function and Campaign_ReadHeroFile() (below) are used to read data for characters in skirmish mode. The characters attributes and powers are assumed to be at their starting levels.

ModPath is the path of the mod where the characters.dat, objects.dat, and powers.dat file are. It can usually be left blank when used during a running mission.

ForceRead forces the DAT files to be re-read, even if they have been read before.

verbose prints some info as each object is read from the DAT file. Prints a warning for each character entry that lacks an object template entry and for every character listed as having a power that is not in the powers.dat database.

Campaign_ReadCharactersFromSavedGame(FileName='',ModPath='',ForceRead=0,verbose=0)

Reads and returns the character data out of a saved game file. This is the best way to get information about the recruited heroes during a campaign mission. There are no saved games for skirmish matches.

In addition to the information about characters returned by Campaign_ReadCharacterData(), Campaign_ReadCharactersFromSavedGame() also returns entries for the teams prestige ('PP'), the text name of the saved game that shows up in the save and load dialogs ('saveName'), and the mission DAT file from that was running when the game was saved ('missionDATFile').

FileName is the full path of the saved game file. It can usually be left blank when used during a running mission. When it is blank (the default empty string), it will try and determine the most recently saved game that was saved from a mission that comes no later in the campaign than the current mission.

ModPath is the path of the mod where the objects.dat and powers.dat file are. They need to be read to fill in the built-in characters' powers and object attributes from the saved game file. It can usually be left blank when used during a running mission.

ForceRead forces the DAT files to be re-read, even if they have been read before.

verbose prints some info as each object is read from the DAT file. Prints a warning for each character entry that lacks an object template entry and for every character listed as having a power that is not in the powers.dat database.

Campaign_ReadHeroFile(FileName,ForceRead=0,verbose=0)

Reads and returns the character data out of a single hero file.

FileName a hero file. This needs to be specified, although it does not have to be a complete path. If the string given isn't a file, the function assumes it is just the basename of the hero file (e.g. "Mister SPiff.hero") and tries to find that file in the hero files directory for the current mod. (*** Don't know about FFvsT3R under Win9X systems yet.)

This function and Campaign_ReadCharacterData() (above) are used to read data for characters in skirmish mode.

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

verbose prints some info as the data is read from the hero file.

The Dictionary

Each of these functions 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.

Most of the keys for each character dictionary are pretty self-explanatory and most are named similarly to their corresponding FFEdit entries. Below are three examples.

 

Examples

Example 1, A Typical Built-In Entry From a Saved Game

To start off, here we read and print Liberty Lad's data from a game saved during the Robots on the Rampage missions.

Code:
>>> import datfiles
>>> sgd = datfiles.Campaign_ReadCharactersFromSavedGame()
>>> print sgd['liberty_lad'] 

This will return something like

Code:

print sgd['liberty_lad']

{'powers': {'liberty Backflip': {'PowerName': 'liberty Backflip', 'Stun': 0, 'RangeMax': 5, 'SpecialType': 0, 'Speed': 3, 'AttackFlags': 128, 'EPCost': 3, 'RangeMin': 1, 'Radius': 0, 'Magnitude': 3, 'SubType': 0, 'notForCustom': 0, 'animation': 'melee_2', 'DamageType': 0, 'FX': '', 'MaxInstances': 0, 'Knockback': 3, 'Accuracy': 0, 'PowerType': 1}, 'liberty Tumble': {'PowerName': 'liberty Tumble', 'Stun': 0, 'RangeMax': 3, 'SpecialType': 7, 'Speed': 3, 'AttackFlags': 0, 'EPCost': 4, 'RangeMin': 1, 'Radius': 0, 'Magnitude': 0, 'SubType': 0, 'notForCustom': 0, 'animation': 'special_2', 'DamageType': 1, 'FX': 'libertylad_tumble', 'MaxInstances': 0, 'Knockback': 0, 'Accuracy': 0, 'PowerType': 7}, 'liberty Exploding Spitball': {'PowerName': 'liberty Exploding Spitball', 'Stun': 0, 'RangeMax': 1, 'SpecialType': 0, 'Speed': 1, 'AttackFlags': 2, 'EPCost': 4, 'RangeMin': 0, 'Radius': 2, 'Magnitude': 4, 'SubType': 3, 'notForCustom': 0, 'animation': 'ranged_2', 'DamageType': 12, 'FX': 'libertylad_grenade', 'MaxInstances': 0, 'Knockback': 0, 'Accuracy': 3, 'PowerType': 2}, 'liberty Proximity Grenade': {'PowerName': 'liberty Proximity Grenade', 'Stun': 1, 'RangeMax': 1, 'SpecialType': 0, 'Speed': 1, 'AttackFlags': 514, 'EPCost': 5, 'RangeMin': 0, 'Radius': 2, 'Magnitude': 3, 'SubType': 3, 'notForCustom': 0, 'animation': 'ranged', 'DamageType': 3, 'FX': 'libertylad_proximitygrenade', 'MaxInstances': 0, 'Knockback': 1, 'Accuracy': 3, 'PowerType': 2}, 'liberty One Two': {'PowerName': 'liberty One Two', 'Stun': 1, 'RangeMax': 5, 'SpecialType': 0, 'Speed': 3, 'AttackFlags': 0, 'EPCost': 0, 'RangeMin': 1, 'Radius': 0, 'Magnitude': 2, 'SubType': 0, 'notForCustom': 0, 'animation': 'melee', 'DamageType': 0, 'FX': 'libertylad_onetwo', 'MaxInstances': 0, 'Knockback': 1, 'Accuracy': 0, 'PowerType': 1}, 'liberty Energy Grenade': {'PowerName': 'liberty Energy Grenade', 'Stun': 0, 'RangeMax': 1, 'SpecialType': 0, 'Speed': 1, 'AttackFlags': 2, 'EPCost': 4, 'RangeMin': 0, 'Radius': 1, 'Magnitude': 3, 'SubType': 3, 'notForCustom': 0, 'animation': 'ranged_2', 'DamageType': 2, 'FX': 'libertylad_grenade', 'MaxInstances': 0, 'Knockback': 2, 'Accuracy': 3, 'PowerType': 2}, 'liberty Molecular Excitation': {'PowerName': 'liberty Molecular Excitation', 'Stun': 0, 'RangeMax': 1, 'SpecialType': 6, 'Speed': 2, 'AttackFlags': 0, 'EPCost': 3, 'RangeMin': 0, 'Radius': 0, 'Magnitude': 3, 'SubType': 0, 'notForCustom': 0, 'animation': 'special', 'DamageType': 1, 'FX': 'libertylad_molecularexcite', 'MaxInstances': 0, 'Knockback': 0, 'Accuracy': 0, 'PowerType': 4}, 'liberty Stun Grenade': {'PowerName': 'liberty Stun Grenade', 'Stun': 5, 'RangeMax': 2, 'SpecialType': 0, 'Speed': 1, 'AttackFlags': 2, 'EPCost': 5, 'RangeMin': 0, 'Radius': 2, 'Magnitude': 0, 'SubType': 3, 'notForCustom': 0, 'animation': 'ranged', 'DamageType': 0, 'FX': 'libertylad_stungrenade', 'MaxInstances': 0, 'Knockback': 0, 'Accuracy': 3, 'PowerType': 2}, 'liberty Taunt': {'PowerName': 'liberty Taunt', 'Stun': 0, 'RangeMax': 1, 'SpecialType': 0, 'Speed': 3, 'AttackFlags': 0, 'EPCost': 4, 'RangeMin': 0, 'Radius': 3, 'Magnitude': 4, 'SubType': 0, 'notForCustom': 0, 'animation': 'area', 'DamageType': 12, 'FX': 'libertylad_taunt', 'MaxInstances': 0, 'Knockback': 0, 'Accuracy': 0, 'PowerType': 3}}, 'camp_only': 1, 'charName': 'liberty_lad', 'mass': 55.0, 'complex': 230.0, 'movementRadius': 1.0, 'NIF': 'library\\characters\\liberty_lad\\character.nif', 'endurance': 2, 'powerLevels': {'liberty Backflip': 0, 'liberty Tumble': 0, 'liberty Exploding Spitball': 0, 'liberty Proximity Grenade': 0, 'liberty One Two': 3, 'liberty Energy Grenade': 2, 'liberty Molecular Excitation': 0, 'liberty Stun Grenade': 3, 'liberty Taunt': 3}, 'templateName': 'liberty_lad', 'VID': 'LL', 'activeAttributes': 3, 'strength': 3, 'class': 'GAME_OBJ_HERO', 'speed': 5, 'CP': 6, 'XP': 2000, 'isCustom': 0, 'CSBase': '', 'characterAttributes': ['nimble', 'jumper', 'danger sense'], 'objectAttributes': ['complex', 'class', 'NIF', 'material', 'templateName', 'mass', 'pickupDistance', 'elasticity'], 'elasticity': 0.0, 'material': 0.0, 'energy': 3, 'AI': 'CGenericHero', 'alterEgo': '', 'tier_a': ['liberty One Two', 'liberty Taunt', 'liberty Backflip', 'liberty Tumble', 'liberty Exploding Spitball'], 'tier_b': ['liberty Stun Grenade', 'liberty Energy Grenade', 'liberty Proximity Grenade', 'liberty Molecular Excitation'], 'pickupDistance': 2.5, 'agility': 6}

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

And, for testing, the ShowHero() function is useful. Note that the second ShowHero() parameter (by default 1) can be set to 0 to turn off display of the full power entries. (As can be seen above, Liberty Lad has many powers.)

Code:
 >>> sgd=datfiles.Campaign_ReadCharactersFromSavedGame() 
>>> datfiles.ShowHero(sgd['liberty_lad'],0) 
            charName : liberty_lad 
            isCustom : 0 
            strength : 3 
               speed : 5 
             agility : 6 
           endurance : 2 
              energy : 3 
                 VID : LL 
                  AI : CGenericHero 
                 NIF : library\characters\liberty_lad\character.nif 
            material : 0.0 
                mass : 55.0 
            alterEgo : 
    activeAttributes : 3 
 characterAttributes : ['nimble', 'jumper', 'danger sense'] 
    objectAttributes : ['complex', 'class', 'NIF', 'material', 'templateName', 'mass', 'pickupDistance', 'elasticity'] 
      movementRadius : 1.0 
               class : GAME_OBJ_HERO 
              CSBase : 
                  XP : 2000 
                  CP : 6 
              tier_a : ['liberty One Two', 'liberty Taunt', 'liberty Backflip', 'liberty Tumble', 'liberty Exploding Spitball'] 
              tier_b : ['liberty Stun Grenade', 'liberty Energy Grenade', 'liberty Proximity Grenade', 'liberty Molecular Excitation'] 
         powerLevels : {'liberty Backflip': 0, 'liberty Tumble': 0, 'liberty Exploding Spitball': 0, 'liberty Proximity Grenade': 0, 'liberty One Two': 3, 'liberty Energy Grenade': 2, 'liberty Molecular Excitation': 0, 'liberty Stun Grenade': 3, 'liberty Taunt': 3} 
             complex : 230.0 
          elasticity : 0.0 
      pickupDistance : 2.5 
       powers.keys() : ['liberty Backflip', 'liberty Tumble', 'liberty Exploding Spitball', 'liberty Proximity Grenade', 'liberty One Two', 'liberty Energy Grenade', 'liberty Molecular Excitation', 'liberty Stun Grenade', 'liberty Taunt']

Example 2, A Typical Starting Built-In Entry

Similarly, we can get a built-in character's starting data using Campaign_ReadCharacterData().

>>> import datfiles
>>> startchars=datfiles.Campaign_ReadCharacterData()
>>> print startchars['el_tanko']

{'powers': {'el_tanko old one-two-three': {'PowerName': 'el_tanko old one-two-three', 'Stun': 1, 'RangeMax': 5, 'SpecialType': 0, 'Speed': 3, 'AttackFlags': 0, 'EPCost': 0, 'RangeMin': 1, 'Radius': 0, 'Magnitude': 3, 'SubType': 0, 'notForCustom': 0, 'animation': 'melee_4', 'DamageType': 0, 'FX': 'rainbow_fist', 'MaxInstances': 0, 'Knockback': 0, 'Accuracy': 0, 'PowerType': 1}, 'el_tanko fall down': {'PowerName': 'el_tanko fall down', 'Stun': 0, 'RangeMax': 2, 'SpecialType': 32, 'Speed': 0, 'AttackFlags': 0, 'EPCost': 3, 'RangeMin': 0, 'Radius': 0, 'Magnitude': 4, 'SubType': 0, 'notForCustom': 0, 'animation': 'ranged_3', 'DamageType': 0, 'FX': 'generic_direct01', 'MaxInstances': 0, 'Knockback': 0, 'Accuracy': 2, 'PowerType': 4}, 'el_tanko weakest link': {'PowerName': 'el_tanko weakest link', 'Stun': 2, 'RangeMax': 5, 'SpecialType': 24, 'Speed': 4, 'AttackFlags': 0, 'EPCost': 4, 'RangeMin': 1, 'Radius': 0, 'Magnitude': 4, 'SubType': 0, 'notForCustom': 0, 'animation': 'melee_3', 'DamageType': 4, 'FX': 'krunk', 'MaxInstances': 0, 'Knockback': 0, 'Accuracy': 0, 'PowerType': 1}, 'el_tanko heal': {'PowerName': 'el_tanko heal', 'Stun': 0, 'RangeMax': 2, 'SpecialType': 18, 'Speed': 1, 'AttackFlags': 0, 'EPCost': 4, 'RangeMin': 0, 'Radius': 0, 'Magnitude': 4, 'SubType': 1, 'notForCustom': 0, 'animation': 'area', 'DamageType': 4, 'FX': 'orcolajet', 'MaxInstances': 0, 'Knockback': 0, 'Accuracy': 3, 'PowerType': 2}, 'el_tanko free will and motion': {'PowerName': 'el_tanko free will and motion', 'AttackModesBlocked': 152, 'DamageTypesBlocked': 1536, 'notForCustom': 0, 'BlockType': 1, 'DefenceFlags': 1, 'Success': 4, 'PowerType': 6}}, 'camp_only': 1, 'charName': 'el_tanko', 'mass': 250.0, 'complex': 155.0, 'movementRadius': 1.0, 'skin': 'standard', 'NIF': 'custom_characters\\male_heavy\\character.nif', 'endurance': 5, 'powerLevels': {'el_tanko old one-two-three': 1, 'el_tanko fall down': 1, 'el_tanko weakest link': 1, 'el_tanko heal': 1, 'el_tanko free will and motion': 1}, 'templateName': 'el_tanko', 'VID': 'OR', 'activeAttributes': 2, 'strength': 5, 'class': 'GAME_OBJ_HERO', 'speed': 6, 'isCustom': 0, 'CSBase': '', 'characterAttributes': ['density control', 'strange visitor', 'superhealer', 'blitzkrieg'], 'tier_b_start': 1, 'tier_a_start': 1, 'objectAttributes': ['class', 'NIF', 'material', 'templateName', 'mass', 'complex', 'skin'], 'attrib_start': 2, 'material': 1.0, 'energy': 4, 'AI': 'CGenericHero', 'alterEgo': '', 'tier_a': ['el_tanko old one-two-three', 'el_tanko fall down'], 'tier_b': ['el_tanko free will and motion', 'el_tanko weakest link', 'el_tanko heal'], 'agility': 7}

And, again in the more human-friendly ShowHero() form, this time showing the powers as well.

Code:
 >>> datfiles.ShowHero(startchars['el_tanko']) 
            charName : el_tanko 
            isCustom : 0 
            strength : 5 
               speed : 6 
             agility : 7 
           endurance : 5 
              energy : 4 
                 VID : OR 
                  AI : CGenericHero 
                 NIF : custom_characters\male_heavy\character.nif 
            material : 1.0 
                mass : 250.0 
                skin : standard 
            alterEgo : 
        attrib_start : 2 
    activeAttributes : 2 
 characterAttributes : ['density control', 'strange visitor', 'superhealer', 'blitzkrieg'] 
    objectAttributes : ['class', 'NIF', 'material', 'templateName', 'mass', 'complex', 'skin'] 
      movementRadius : 1.0 
               class : GAME_OBJ_HERO 
              CSBase : 
        tier_a_start : 1 
              tier_a : ['el_tanko old one-two-three', 'el_tanko fall down'] 
        tier_b_start : 1 
              tier_b : ['el_tanko free will and motion', 'el_tanko weakest link', 'el_tanko heal'] 
         powerLevels : {'el_tanko old one-two-three': 1, 'el_tanko fall down': 1, 'el_tanko weakest link': 1, 'el_tanko heal': 1, 'el_tanko free will and motion': 1} 
             complex : 155.0 
       powers.keys() : ['el_tanko old one-two-three', 'el_tanko fall down', 'el_tanko weakest link', 'el_tanko heal', 'el_tanko free will and motion'] 
el_tanko old one-two-three: 
   PowerName = el_tanko old one-two-three 
   PowerType = PT_MELEE 
   SubType = PT_ATTACK_SUBTYPE_NONE 
   EPCost = none 
   animation = melee_4 
   FX = rainbow_fist 
   Magnitude = medium 
   DamageType = PT_DAMAGE_CRUSH 
   Speed = fast 
   Stun = low 
   Knockback = none 
   RangeMin = medium 
   RangeMax = 5 
   Accuracy = very low 
   Radius = none 
   SpecialType = PT_SPECIAL_NONE 
   MaxInstances = 0 
   AttackFlags = 
   notForCustom = 0 
el_tanko fall down: 
   PowerName = el_tanko fall down 
   PowerType = PT_DIRECT 
   SubType = PT_ATTACK_SUBTYPE_NONE 
   EPCost = very low 
   animation = ranged_3 
   FX = generic_direct01 
   Magnitude = high 
   DamageType = PT_DAMAGE_CRUSH 
   Speed = very slow 
   Stun = none 
   Knockback = none 
   RangeMin = short 
   RangeMax = long 
   Accuracy = medium 
   Radius = none 
   SpecialType = PT_SPECIAL_DENSITY_MAXIMIZE 
   MaxInstances = 0 
   AttackFlags = 
   notForCustom = 0 
el_tanko weakest link: 
   PowerName = el_tanko weakest link 
   PowerType = PT_MELEE 
   SubType = PT_ATTACK_SUBTYPE_NONE 
   EPCost = low 
   animation = melee_3 
   FX = krunk 
   Magnitude = high 
   DamageType = PT_DAMAGE_RADIATION 
   Speed = very fast 
   Stun = medium 
   Knockback = none 
   RangeMin = medium 
   RangeMax = 5 
   Accuracy = very low 
   Radius = none 
   SpecialType = PT_SPECIAL_ALTER_GENETIC_STRUCTURE 
   MaxInstances = 0 
   AttackFlags = 
   notForCustom = 0 
el_tanko heal: 
   PowerName = el_tanko heal 
   PowerType = PT_RANGED 
   SubType = PT_ATTACK_SUBTYPE_BEAM 
   EPCost = low 
   animation = area 
   FX = orcolajet 
   Magnitude = high 
   DamageType = PT_DAMAGE_RADIATION 
   Speed = slow 
   Stun = none 
   Knockback = none 
   RangeMin = short 
   RangeMax = long 
   Accuracy = high 
   Radius = none 
   SpecialType = PT_SPECIAL_LIFE_GIFT 
   MaxInstances = 0 
   AttackFlags = 
   notForCustom = 0 
el_tanko free will and motion: 
   PowerName = el_tanko free will and motion 
   PowerType = PT_PASSIVE_DEFENCE 
   BlockType = PT_BLOCK_TYPE_NORMAL 
   DamageTypesBlocked = PT_DAMAGE_BLOCKED_MENTAL PT_DAMAGE_BLOCKED_MYSTICAL 
   AttackModesBlocked = PT_AREA_BLOCKED PT_SPECIAL_BLOCKED PT_DIRECT_BLOCKED 
   DefenceFlags = PT_DEFENCE_FLAG_INACTIVE 
   Success = PT_BLOCK_SUCCESS_ALWAYS 
   notForCustom = 0 

As is clear, the entries are very similar, which is the idea.

Example 3, A Typical Custom Entry

Finally, a custom hero read in with Campaign_ReadHeroFile():

Code:
>>> wildfire=datfiles.Campaign_ReadHeroFile('wildfire .hero') 
>>> datfiles.ShowHero(wildfire) 
                name : wildfire 
            charName : 
            isCustom : 1 
    BaseTemplateName : generic_hero 
            strength : 4 
               speed : 5 
             agility : 3 
           endurance : 5 
              energy : 5 
                 VID : MB 
                  AI : CGenericHero 
                 NIF : custom_characters\male_basic_wildfire\character.nif 
            material : 5.0 
                mass : 80.0 
                skin : wildfire 
            alterEgo : 
    activeAttributes : 5 
 characterAttributes : ['dispersed', 'flier', 'invulnerable', 'light speed', 'grim resolve'] 
    objectAttributes : ['mass', 'material', 'complex'] 
      movementRadius : 1.4573504029e-043 
               class : GAME_OBJ_HERO 
              CSBase : 
                  XP : 0 
                  CP : 0 
         powerLevels : {'annihilation ': 1, 'energy blast': 1, 'punch': 1} 
             complex : 1099.0 
       powers.keys() : ['annihilation ', 'energy blast', 'punch'] 
annihilation : 
   PowerName = annihilation 
   PowerType = PT_AREA 
   SubType = PT_ATTACK_SUBTYPE_NONE 
   EPCost = medium 
   animation = area 
   FX = evilmale_annhilate 
   Magnitude = extreme 
   DamageType = PT_DAMAGE_ENERGY 
   Speed = normal 
   Stun = none 
   Knockback = high 
   RangeMin = short 
   RangeMax = medium 
   Accuracy = very low 
   Radius = large 
   SpecialType = PT_SPECIAL_NONE 
   MaxInstances = 0 
   AttackFlags = 
   notForCustom = 0 
energy blast: 
   PowerName = energy blast 
   PowerType = PT_RANGED 
   SubType = PT_ATTACK_SUBTYPE_BEAM 
   EPCost = very low 
   animation = ranged_4 
   FX = pulse_bolts_beam2 
   Magnitude = high 
   DamageType = PT_DAMAGE_ENERGY 
   Speed = fast 
   Stun = medium 
   Knockback = none 
   RangeMin = short 
   RangeMax = long 
   Accuracy = high 
   Radius = none 
   SpecialType = PT_SPECIAL_NONE 
   MaxInstances = 0 
   AttackFlags = 
   notForCustom = 0 
punch: 
   PowerName = punch 
   PowerType = PT_MELEE 
   SubType = PT_ATTACK_SUBTYPE_NONE 
   EPCost = trace 
   animation = melee 
   FX = default_punch 
   Magnitude = low 
   DamageType = PT_DAMAGE_CRUSH 
   Speed = fast 
   Stun = low 
   Knockback = none 
   RangeMin = medium 
   RangeMax = long 
   Accuracy = very low 
   Radius = none 
   SpecialType = PT_SPECIAL_NONE 
   MaxInstances = 0 
   AttackFlags = 
   notForCustom = 0 

 

Notes

First, for these functions to work, there must actually be an objects.dat, characters.dat, powers.dat, and so on files unzipped in the right spots, either in the mod folder for whatever mission is running or where specified by FileName. There is a facility in FFvsT3R to unzip these files from a python script, but it is unavailable in the first game.

I wrote this module with an eye on keeping delays minimal. Because of that, each of these functions tries to avoid re-reading the DAT, hero, or saved game files 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, a full Campaign_ReadCharacterData() read of the three principle DAT files from the Strangers campaign (which has pretty big DAT files), a saved game file and a hero file all took about 1.25 seconds, en toto. Subsequent calls are much faster (under a millisecond). Because of this initial delay, if these functions are to be called during a mission, it is a good idea to put a call to Campaign_ReadCharacterData() and Campaign_ReadCharactersFromSavedGame() in onPostInit() right 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.