Wednesday, May 31, 2006

Generic Datafile Format

While rewriting the engine in D language, I found out that most datafiles I use in TCOD can use the same format. While each datafile had its own parser in the C version of the engine, the D version will have a unique generic parser which build a structure of generic objects that can be easily used either directly by the engine or translated to more specific objects.

Here is the grammar of the .dat format :

The file is a list of entity declarations :
DatFile ::= Entity DatFile | empty

An entity is a type, a facultative name, and a content delimited by braces :
Entity ::= EntityType [EntityName] '{' EntityContent '}'
EntityType ::= IDENTIFIER // for example MyEntityType
EntityName ::= STRING // for example "entityName"

The content is a list of entity items :
EntityContent ::= EntityItem EntityContent | empty

An item can either be another entity declaration, or property (a name/value pair) or a flag :
EntityItem ::= Entity | Property | Flag
Property ::= PropertyName '=' PropertyValue
Flag ::= IDENTIFIER
PropertyName ::= IDENTIFIER
PropertyValue ::= STRING | INTEGER | FLOAT | 'false' | 'true'

There are int, float, string and boolean properties. Some have more specific type like color, but rely on one of those basic type. For example, a color is a string with a specific format "255,0,255" or "#FF00FF". There is also a dice format which is a string like "3D6-4".

The flags are just a shortcut for boolean property. You can convert any boolean property into a flag. Thus, you will write :
MyEntity { myFlag }
instead of :
MyEntity { myFlag=true }


That's it. Now I have a generic parser which create an array of entities from a .dat file. For each file to parse, I create a parser and defines the corresponding grammar :
  • the list of entity types
  • for each type, the list of properties, flags, sub-entities

Some example of .dat files :

global configuration file (extract) :
// colors used by the game engine.
colors {
title="192,96,24"
credits="#FF8000"
}

// game engine parameters
engine {
// delay in milliseconds between each frame
ticks=100
}

// screen layout
layout {
conWidth=80 // console width
conHeight=50 // console height
mapWidth=40 // visible map width
mapHeight=40 // visible map height
}

The item definition file (extract) :
itemType "clothes" {
char='['
abstract
inventory
feature "wear" { onoff=false }
quality="low"
itemType "jackets" {
abstract
cost=20
feature "wear" { bodyPart="torso" }
itemType "a common shirt" {}
}

itemType "armors" {
abstract
inventory
char='['
feature "wear" { onoff=false }
feature "armor" { condition "when_wear" {} }
itemType "light armor" {
abstract
inventory
itemType "leather" {
abstract
feature "armor" { bonus=1 spell=10 }
quality="low"
itemType "leather leggings" { feature "wear" { bodyPart= "legs" } cost=15 }
itemType "leather gloves" { feature "wear" { bodyPart= "hands"} cost=10 }
itemType "a leather cuirass" { feature "wear" { bodyPart= "torso"} cost=25 }
itemType "a leather helmet" { feature "wear" { bodyPart= "head" } cost=15 }
itemType "a leather left bracer" { feature "wear" { bodyPart= "leftWrist"} cost=5 }
itemType "a leather right bracer" { feature "wear" { bodyPart= "rightWrist" } cost=5 }
itemType "leather boots" { feature "wear" { bodyPart= "feet" } cost=10 }
}
}
}

itemType "weapons" {
char='['
abstract
inventory
feature "wear" { bodyPart="rightHand" onoff=false }
feature "attack" { condition "when_wear" {} criticalProb=5 criticalCoef=2 minDmg=1 }
itemType "short blade" {
abstract
inventory
durability=4
feature "attack" { busy=2 }
itemType "a dagger" {
cost=20
weight=0.5
quality="low"
feature "attack" { maxDmg=4 criticalProb=10 }
}
}
}
}

The creatures definition file (extract) :
creature "vermin" {
abstract
char='r'
creature "rat" {
weight=0.5 height=20 speed=20
STR=2 DEX=15 CON=10 INT=2 WIS=12 CHA=2
hpmin=1 hpmax=2
skill "hide" { level=14 }
attack "bite" { modifier=4 delay=20 dice="1d4-3" }
armor=14
remains "raw rat meat" { nb=1 }
}
}