Basilisk Data File Format

Basilisk consists of things, categories, and rules. This section will describe the format used to define things and categories. To read about how to write rules, see the scripting section.

Basilisk requires that all attributes used in any definition of any thing be predeclared via the "attribute" statement. Any attributes not predeclared will generate errors during the parse of your data file.

Basilisk also supports units (like pounds, dollars, inches, meters, years, etc.), and allows you to convert between units easily and quickly. You do not need to use units to use Basilisk, but they come in very handy.

For more information, click on a chapter heading:

  1. Identifiers
  2. Units
  3. Attributes
  4. Things
  5. Categories
  6. Including Files
  7. Conclusion

1. Identifiers

The term "identifier" will be used extensively in this document. It is essentially a term for a name given to an object. For example, your name is an identifier that identifies you.

In the case of Basilisk, all identifiers must be unique. You cannot have two separate things both named "Sword", for instance. You must make the identifiers unique by making them more descriptive: "shortSword" versus "longSword", for example.

Note that those two identifiers both lacked a space to separate the words. This is because identifiers cannot contain spaces. They also cannot contain dashes, slashes, asterisks, ampersands, or any other punctuation. In fact, the only valid characters you can put in an identifier are letters, numbers, and the underscore ('_'), and numbers can never be the first characters in an identifier.

Examples of valid identifiers:

Examples of invalid identifiers are: Also, Basilisk is case-insensitive. What this means, is that whether you type your identifier as "longSword", or "LONGSWORD", or "lOnGsWoRd", it's all the same to Basilisk. Upper-case and lower-case make no difference, except in strings (see the section on Attributes, below, for more on strings).

2. Units

To put it simply, units describe numbers. If you ask someone (rather impertinently) how much they weigh, they are unlikely to just say "140". If they did, you might wonder if that was in pounds, kilograms, etc. The unit "pounds" is critical in this case to clarify the meaning of their answer.

Likewise, if you were playing a game of D&D, and the DM says to you, "you just found 64!" 64 what? It would make a big difference if he said "copper pieces" versus "platinum pieces."

Basilisk allows you to define units that you will use in your data file. For example:

unit cp;
unit sp = 10 cp;
unit gp = 10 sp;
unit pp = 10 gp;
What this tells you is that 1 sp ("silver piece") is worth 10 cp ("copper pieces"), that 1 gp ("gold piece") is worth 10 sp, and that 1 pp ("platinum piece") is worth 10 gp. This, as many of you will recognize, is the default monetary system of the D&D universe.

The benefit of explicitly defining units in relation to other units (as in the example above), is that you can easily request the conversion of, say, 1552 cp into pp (1.552 pp). Basilisk will perform the work of converting it for you.

You could also do the following:

unit second;
unit round = 6 second;
unit minute = 10 round;
unit hour = 60 minute;
unit day = 24 hour;
The above is easily recognized as defining units of time. Note, however, that the units are not pluralized when they are referred to (i.e., it says "6 second", rather than "6 seconds"). This is because the plural form ("seconds" vs. "second") has not been defined as a valid unit. This last example shows how to use plurals validly:
unit second;
unit seconds = 1 second;
unit round = 6 seconds;
unit rounds = 1 round;
unit minute = 10 rounds;
unit minutes = 1 minute;
unit hour = 60 minutes;
unit hours = 1 hour;
unit day = 24 hours;
unit days = 1 day;
Note that you could also just declare the plural form (without the singular), but you would then wind up saying that a particular spell, for example, lasts for "1 rounds". If you want your files to sound grammatically correct, your only real option is to either use abbreviations ("cp" vs. "copper piece") or to define both the singular and plural forms of each unit.

3. Attributes

All attributes that will be used to define any thing in any of your files must be declared before you use them. This is accomplished by using the "attribute" statement.

An "attribute" statement lets you associate a given identifier with a type. The identifier must be unique in the file, and only be used to reference an attribute of a thing.

Here's some examples of attribute definitions:

attribute name string
attribute weight number
attribute isSmart boolean
attribute family thing
attribute children category
attribute nameRoutine rule
Attributes may only be of the following types: Give careful thought to your data model before you proceed, so that you can select the set of attributes that you will need for the things you create.

4. Things

Things are the building blocks of the universe, and so you'll find that your data files will be full of them. Because they are so ubiquitous, there are lots of ways to create them. You'll want to learn all the possible ways, because they each have their strengths and weaknesses.

The first way is the most straightforward:

longSword
	.name "longsword"
	.cost 15 gp
	.weight 4 lb
	.size medium
	.damage 1d8
	.criticalRange 19
	.criticalMultiplier 2
end
This example assumes that "name", "cost", "weight", "size", "damage", "criticalRange", and "criticalMultiplier" have all been previously declared with the attribute keyword, and that "gp" and "lb" have both been previously declared as units.

The example shows how you could define a longsword, with all of it's attributes. You could then do the same thing for a flail, nunchaku, a quarterstaff, and so forth.

If you did so, however, you would quickly realize that you were typing the same attributes over and over -- name, cost, weight, size, damage, etc. being fairly common to most weapons. If you find yourself entering attribute names over and over, you might want to look at the "template" statement.

Templates allow you to abbreviate much of your data entry. Here's an example:

template { name cost damage criticalRange 
           criticalMultiplier weight size }
	weaponClub { "club" $ 1d6 20 2 3 lb medium }
	weaponDagger { "dagger" 2 gp 1d4 19 2 2 lb tiny }
	weaponDart { "dart" 5 sp 1d4 20 2 1/2 lb small }
end
Notice how much easier that was! You simply specify (in the curly braces after the "template" keyword) the list of attributes you want to define for each thing, and then you list each thing, followed by each of its attribute values in curly braces. You will also notice the weaponClub thing -- instead of a price, it has a '$' character. This means that a club has no price (not just "0 gp", but literally no price attribute), and that the parser should ignore that attribute for this thing.

The last method for entering things requires another example to demonstrate:

flailSpecialAttack
	.name "bonus"
	.description "+2 to disarm attempts"
end

weaponFlail
	.name "flail"
	.cost 8 gp
	.damage 1d8
	.criticalRange 20
	.criticalMultiplier 2
	.weight 5 lb
	.size medium
	.bonus flailSpecialAttack
end
In this case, a flail has a bonus that grants a +2 to disarm attempts. This bonus is a thing, because it has attributes that describe it. However, because that bonus is only ever used once in the file, it seems a waste to even bother naming it, and it takes up 4 lines of the file!

Anonymous things are things that are actually embedded within the definition of another thing or category. They are not named (hence the term "anonymous"), and take up much less space in the data file. They are also faster to parse. Here's the same data as the example above, but with the "flailSpecialAttack" thing made anonymous:

weaponFlail
	.name "flail"
	.cost 8 gp
	.damage 1d8
	.criticalRange 20
	.criticalMultiplier 2
	.weight 5 lb
	.size medium
	.bonus { .name "bonus" 
					 .description "+2 to disarm attempts" }
end
All we had to do was enclose the thing's attribute list in curly braces and put the result where we put the identifier before -- voila! An anonymous thing! You can use anonymous things anywhere that you would have referred to a thing's identifier before. Note, however, that when you use an anonymous thing, you cannot refer to it again later, since it has no identifier. Thus, you do not want to use an anonymous thing when the thing will need to be referenced multiple times in a data file.

One final note about things: you may define and redefine the same thing over and over. If a thing is encountered that has already been defined, the attributes in the second definition are added to the thing's previous attribute list. It is very important to note that a thing may have multiple of the same attribute, so a redefinition will never overwrite an existing attribute! Be very careful with this, as the parser will not even issue a warning when you are redefining a thing.

5. Categories

Whereas things are the building blocks of the universe, categories are the glue that hold those blocks together. Things and categories really are inseparable in a well-designed data file.

A category, in it's simplest form, has the following format:

attribute name string
attribute birth string

template { name birth }
  Jamis { "Jamis Gordon Buck"  "25 Jul 1974" }
  Eric { "Eric Victor Bristow" "4 Feb 1976" }
  Nicole { "Nicole Suzanne Buck" "30 Apr 1976" }
  Emily { "Emily Rene Bristow" "28 Aug 1978" }
  Rebecca { "Rebecca Lynn Bristow" "3 Dec 1979" }
  Andrew { "Andrew Michael Bristow" "8 May 1986" }
end

category familyMembers
  Jamis
  Eric
  Nicole
  Emily
  Rebecca
  Andrew
end
This puts "Jamis," "Eric," "Nicole," "Emily," "Rebecca," and "Andrew" grouped together in a category named "familyMembers."

A shorter way to do this, if you are using templates, is as follows:

template in ( familyMembers ) { name birth }
  Jamis { "Jamis Gordon Buck"  "25 Jul 1974" }
  Eric { "Eric Victor Bristow" "4 Feb 1976" }
  Nicole { "Nicole Suzanne Buck" "30 Apr 1976" }
  Emily { "Emily Rene Bristow" "28 Aug 1978" }
  Rebecca { "Rebecca Lynn Bristow" "3 Dec 1979" }
  Andrew { "Andrew Michael Bristow" "8 May 1986" }
end
The "in" keyword, followed by the parenthesis after the "template" keyword lists all the categories that all things defined by this template will belong to. The categories do not have to have been previously defined -- if they are not defined when the parser reads the template, a new category by that name is created. Multiple categories may be specified here -- just separate each category name by at least one space. You can further define each individual member of the template to be in other categories, by putting a parenthetical list of category identifiers after the thing identifier, like so:
template ( familyMembers ) { name birth }
  Jamis in ( buckFamily ) { "..." "..." }
  Eric in ( bristowFamily ) { "..." "..." }
  Nicole in ( buckFamily ) { "..." "..." }
  Emily in ( bristowFamily ) { "..." "..." }
  Rebecca in ( bristowFamily ) { "..." "..." }
  Andrew in ( bristowFamily ) { "..." "..." }
end
This, then, says that Jamis, Eric, Nicole, Emily, Rebecca, and Andrew are all "familyMembers", but that Jamis and Nicole are both in the "buckFamily" category, and Eric, Emily, Rebecca, and Andrew are all in the "bristowFamily" category.

Categories may also be specified when defining things with the basic format, as demonstrated here:

longSword in ( weapons slashingWeapons )
.name "longsword"
.cost 15 gp
.weight 4 lb
.size medium
.damage 1d8
.criticalRange 19
.criticalMultiplier 2
end
The parenthetical list after the thing's identifier states the list of categories that the thing belongs to. If any of the categories have not yet been defined, they will be defined automatically. In this example, the longSword thing is created and put automatically into the "weapons" category and the "slashingWeapons" category.

Note that categories may belong to other categories, just as things (and even rules) do. The syntax is exactly the same as for both things and templates, using the "in" keyword. Here's an example:

category weapons in ( equipableItems )
  ...
end
You may also weight the items in a category to determine their likelihood of being chosen randomly (see the Any function in the scripting section). Weights are much like percentages, but you don't have to make sure that the numbers total to 100. For example, if you wanted to randomly select a gem value, you might use the following category set up:
category groupGemLookup
[50] { .cost 4d4 gp }
[30] { .cost 2d4*10 gp }
[25] { .cost 4d4*10 gp }
[15] { .cost 2d4*100 gp }
[ 9] { .cost 4d4*100 gp }
[ 1] { .cost 2d4*1000 gp }
end
First, note that the things in the category are declared anonymously. Second, notice the numbers in square brackets -- those are the weight value for each thing. The total of the weights, in this case, is 50+30+25+15+9+1=130, and the chance of any of the items being chosen at random is the thing's weight divided by the total weight. So, a 4d4 gp gem will be chosen 50 times out of 130 (about 38.5%), while a 2d4*1000 gp gem will only be chosen roughly once in 130 times (about 0.75%).

Note that if weights are not specified, they default to 1. If a weight is specified, however, it must be an integer value (it cannot have a fractional value, or numbers after the decimal point).

Lastly, just as things may be declared anonymously, so may categories. An anonymous category declaration may be used anywhere a category identifier would be used, and is simply the category members (including weights, if you want them) enclosed in parentheses. For example:

attribute name string
attribute favoriteMovies category

template in ( allMovies ) { name }
Bambi { "Bambi" }
RobinHood { "Robin Hood" }
TrumanShow { "The Truman Show" }
MrHollandsOpus { "Mr. Holland's Opus" }
Contact { "Contact" }
BugsLife { "A Bug's Life" }
ToyStory2 { "Toy Story 2" }
end

Jamis
  .name "Jamis Buck"
  .favoriteMovies ( ToyStory2 
                    BugsLife 
                    TrumanShow 
                    { .name "Secret of Roan Inish" } )
end

Tarasine
  .name "Tarasine Buck"
  .favoriteMovies ( [3] MrHollandsOpus 
                    Contact 
                    TrumanShow )
end
In this example, a list of movies is declared and then, for each person (in this case, me and my wife), a category is declared anonymously, containing our favorite movies. (The contents of the categories are listed one per line for space limitation reasons -- there is no reason they could not be strung out on one line, if you preferred).

I got tricky in my movie list. Perhaps you noticed the anonymous thing, listed in the anonymous category! You can nest them as deep as you need to.

Lastly, notice in my wife's movie list that the movie "Mr. Holland's Opus" is weighted so that it will be chosen 3 times out of 5. The other two movies are left to the default weighting of 1, each being chosen 1 time out of 5. You can use weightings wherever you feel you will need them.

A special thing, "null," may also be used in a category to indicate that the particular category member is non-existant. Any given category may only contain one null member.

One final note about categories: you may define and redefine the same category over and over. If a category is encountered that has already been defined, the members of the second definition are added to the category's previous member list. It is important to note that a category may only have one of any given thing or category, and multiples are silently ignored. Be very careful with this, as the parser will not even issue a warning when you are redefining a category.

6. Including Files

You may have noticed by now that any realistic set of data will be quite large. It is often not going to be reasonable to assume that it could all be managed in one file. Also, there are times when you would like one part of your data to be shared in multiple different databases (one for D&D, one for Star Wars, etc.). How to handle that?

The answer, my friend, lies in Basilisk's ability to include the contents of one file from within another. Here's an example:

#include "coredata.bsk"
C-programmers everywhere should immediately recognize this as shamelessly stolen from that language. All it does, in essence, is temporarily stop parsing the current file, and jump into the file referenced in the quotes. That file is completely parsed into the database, and then the parser jumps back into the original file where it left off and continues.

Any given file may include any number of files, each of which may include files, etc. The Baslisk parser includes a safeguard which will prevent a file from being included more than once, so you don't have to worry about redefining your categories or things, either.

7. Conclusion

Well, that's about the gyst of it. All that remains is for you to master the scripting aspects of Basilisk, and you'll be armed and dangerous!

If you feel you're ready, proceed to the scripting section!