This document describes the monster-compiler (mc). In more precise terms, this is a code-generation tool that is used to create files called monsters_tables.c and monsters_constant.h in gmoria. It reads in a single human-readable file that completely describes the attributes of all of the creatures in the game, and it generates code that is compiled into the game. The compiler issues warnings and errors when things don’t quite make sense. This document describes how to use this tool, and the format of the monster-definition file.
The audience for this tool is a person who wants to add new monsters to moria in a safe way. Although it’s easy to add new monsters, the monster definition file only goes so far; for example it doesn’t allow the adding of new spells, defenses or modes of attack. When the syntax of the monsters definition file is insufficient, the next step is to change actual C code. Beware that adding new monsters to the game breaks save-game compatibility.
The monster-definition file describes all of the monsters in the game. A monster is anything that can attack the character in the game. Although it doesn’t seem very monsterish, a ‘Swordsman’ is a monster. mc reads in this monster definition file to create the monsters_tables.c and monsters_constant.h files. It is meant to be human-readable, easily understood and modified by non-programmers. The file consists of a series of blocks.
The monster-definition file is comprised of blocks. A block looks something like this:
creature "Swordsman" { ... };
As you can see, the block starts with a keyword denoting the kind of block (a creature block). Following that there’s a name for the block (‘"Swordsman"’), and then there are a set of braces, followed by a semicolon. The attributes of the swordsman creature go inside of the braces (instead of ‘...’) .
There are two kinds of blocks: creature blocks and class blocks. A creature canbe derived from a class. A class block looks like:
class humanoid { ... };
It is very similar to the creature block. The name isn’t in quotes because the name of the class will not show up anywhere in the monsters_tables.c file. The attributes that go inside of a class block are the same as the creature block.
The monster-definition file is made up of sequences of these class and creature blocks. A complete monster-defintion file would have many creatures, that derive from many classes which derive from many classes. The use of classes help reduce errors, and increase consistency in the final product. Using too many classes make changing the monster-definition file a trickier thing to accomplish. you will want to strike a balance.
The monster compiler will tell you when you reference a class that doesn’t exist or if you’ve forgotten a semi-colon. It will also tell you if you’ve forgotten an important attribute.
Class blocks are used by creature blocks to derive attributes. For example all dragon creature blocks share a dragon class that make them "dragonish".
You derive a creature from a class like so:
creature "Swordsman": warrior { ... };
You can derive a class from zero or more classes. For example:
class warrior: humanoid { ... };
All of the attributes contained in the humanoid class will be pulled into the warrior class.
Creature blocks are thought of as the most important block, and they relate directly to the monsters_tables.c file. This means you have an "Evil Iggy" in the game because there exists an "Evil Iggy" creature block in the monster-defintion file.
A creature block can derive from zero or more class blocks. For example:
creature "Swordsman": humanoid, swordwarrior { ... };
All of the attributes from the humanoid and swordwarrior class will be present in this creature called ‘Swordsman’. The name ‘Swordsman’ will appear in the game when the player looks at the monster.
Every creature in moria is made up of a set of attributes. Every creature and class block in the monster-definition file describes attributes.
Attributes have a name, and a value, and are always followed by a semicolon. For example:
class humanoid { speed: 1; ac: 1; }
The ‘speed’ and ‘ac’ attributes are set to a value of 1. The semicolon must follow each attribute value, or the compiler be unable to parse the file due to a syntax error.
The allowable attributes are: ‘level’, ‘hd’, ‘letter’, ‘exp’, ‘speed’, ‘ac’, ‘radius’, ‘sleep’, ‘treasure’, ‘defense’, ‘move’, ‘special’, ‘spell’, ‘breath’, ‘resist’ and ‘attack’. Here is an explanation of these attributes – what they do, and what their allowable values are:
This attribute represents which depth the creature will most prominently appear at. A level of ‘1’ is 50 feet, ‘2’ is 100 feet and so on. The value must always an integer between ‘0’ and ‘100’.
class humanoid { level: 1; };
This attribute represents the hitdice of the creature. This is used to describe the maximum hit-points of the creature. A hitdice is two numbers, the number of dice, and the number of sides on a die. It is represented as an integer followed by a bar ‘|’, followed by another integer. For example ‘2|10’. This means ‘2’ dice, with ‘10’ sides. The creature’s hit-points will be somewhere between 2 and 20.
class humanoid { hd: 2|10; };
This attribute represents the ascii character that the creature appears on the screen as. This value is a string, where the first character in that string is the character used to represent the creature onscreen.
class humanoid { letter: "p"; };
This attribute represents how many experience-points that the player receives when killing this creature. A legal value for this attribute is ‘0’ or more.
This attribute represents how fast the creature moves in the game. A legal value for this attribute is ‘0’, ‘1’, ‘2’, or ‘3’. ‘1’ is normal speed.
class humanoid { speed: 2; };
This attribute represents the armour class of the creature. The higher the armour class, the harder it is for the player to hit the creature. A legal value for this attribute is ‘0’ or more, where the largest practical value is ‘125’.
class humanoid { ac: 20; };
This attribute is the area-affect radius of the creature. The creature "notices" things this far away from it. If the creature is asleep and you walk within it’s radius, it has a chance of waking up. A legal value for this attribute is ‘2’ or more, where the largest practical value is ‘40’.
class humanoid { radius: 6; };
This attribute represents how difficult it is to wake up this creature when it is sleeping. Most creatures are sleeping when the level is generated. Legal values for this attribute are ‘0’ or more, where the largest practical value is ‘250’.
class humanoid { sleep: 10; };
This attribute represents what kind of treasure the creature is carrying, if any. The following are legal values for this attribute:
The creature carries small objects.
The creature carries normal sized objects.
The creature carries money.
The creature has money or objects 60% of the time.
The creature has money or obejcts 90% of the time.
The creature holds 1 to 2 objects.
The creature holds 2 to 4 objects.
The creature holds 4 to 8 objects.
The creature holds no objects or money at all.
This attribute can have more than one of these values set at one time. This logical AND is accompished by separating them with a comma (‘,’). A logical NOT is accomplished by prepending ‘~’ to the keyword.
class humanoid { treasure: carry_gold, has_random_60, ~has_random_90; }; class humanoid { treasure: none; };
This attribute represents the susceptibilities of the creature, if any. The following are legal values for this attribute:
The creature is a dragon, and is susceptible to attacks from dragon-slaying (‘SD’) weapons.
The creature is an animal, and is susceptible to attacks from animal-slaying (‘SA’) weapons. It is susceptible to spells and prayers that are designed to work on animals.
The creature is evil, and is susceptible to attacks from evil-slaying (‘SE’) weapons. It is susceptible to spells and prayers that are designed to work on evil creatures.
The creature is undead, and is susceptible to attacks from undead-slaying (‘SU’) weapons. It is susceptible to spells and prayers that are designed to work on undead creatures.
The creature is susceptible to frost-based attacks.
The creature is susceptible to fire-based attacks.
The creature is susceptible to poison-based attacks.
The creature is susceptible to acid-based attacks.
The creature is susceptible to light, or electricity-based attacks.
The creature is susceptible to attacks that involve disolving stone.
This creature cannot be slept or charmed.
This creature can be seen by infra-vision because it has a warm body.
This creature has the maximum number of hit-points, regardless of what it’s hitdice attribute (‘hd’)suggests.
This creature is susceptible to nothing in particular.
This attribute can have more than one of these values set at one time. This logical AND is accompished by separating them with a comma (‘,’). A logical NOT is accomplished by prepending ‘~’ to the keyword.
class humanoid { defenses: evil, undead, acid; }; class humanoid { defenses: none; };
This attribute represents how the creature moves. The following are legal values for this attribute:
The creature doesn’t move at all. It can only attack from where it sits, stands, or hovers.
The creature moves normally.
The creature moves by means of magic, teleporting itself short or long distances periodically.
The creature moves 20% randomly.
The creature moves 40% randomly.
The creature moves 75% randomly.
This attribute can have more than one of these values set at one time. This logical AND is accompished by separating them with a comma (‘,’). A logical NOT is accomplished by prepending ‘~’ to the keyword.
class humanoid { move: move_normal, random_20; };
This attribute represents various abilities that the creature might have. The following are legal values for this attribute:
The creature is invisible, and can only be seen when the character is able to see invisible creatures.
The creature may open doors.
The creature passes through rock.
The creature eats other creatures.
The creature picks up objects or money.
The creature can breed.
Killing this creature makes the player win the game.
There are no special abilities for the creature.
This attribute can have more than one of these values set at one time. This logical AND is accompished by separating them with a comma (‘,’). A logical NOT is accomplished by prepending ‘~’ to the keyword.
class humanoid { special: open_door, picks_up, multiply; }; class humanoid { special: none; };
This attribute describes which spells can be cast if any. The structure of this attribute is slightly different from others in that there is a chance represented along with some keywords that represent spells. The chance represents how often the spell will be casted. The chance may be represented in 2 different ways:
class humanoid { spell 33.3%: blind; };
class humanoid { spell 1/3: blind; };
Only one frequency may only be stated per creature block. This extends to breaths too! A creature can cast the following spells:
Teleport the creature a short distance.
Teleport the creature a longer distance.
Teleport the player to the creature.
Cause the player some light wounds.
Cause the player some serious wounds.
Paralyse the player.
Take away the sight of the player.
Stupify the player.
Make the player afraid.
Summon a monster.
Summon an undead monster.
Make the player move slower.
Reduce the player’s mana points.
The creature casts no spells.
This attribute can have more than one of these values set at one time. This logical AND is accompished by separating them with a comma (‘,’). A logical NOT is accomplished by prepending ‘~’ to the keyword.
class humanoid { spell 1/3: summon_und, slow_per, tel_to; }; class humanoid { spell: none; };
This attribute describes which breaths can be breathed if any. A breath works exactly like a spell, in that it requires a frequency chance. The chance represents how often the breath will be breathed. The chance may be represented in 2 different ways:
class humanoid { breath 33.3%: fire; };
class humanoid { breath 1/3: fire; };
Only one frequency may only be stated per creature block. This extends to spells too! A creature can breathe the following breaths:
Electrcity.
Gaseous poison.
Acrid, corrosive splash.
Extreme cold.
Extreme heat.
The creature doesn’t attack by breathing.
This attribute can have more than one of these values set at one time. This logical AND is accompished by separating them with a comma (‘,’). A logical NOT is accomplished by prepending ‘~’ to the keyword. Breaths imply that the creature is also resistant to that kind of attack. This means that even if the creature block doesn’t describe a ‘resist’ attribute, it will still be set implicitly.
class humanoid { breath 2/3: light, fire; }; class humanoid { breath: none; };
This attribute represents resistance to one of the basic elements. Legal values are the following keywords:
The creature is resistant to electricity.
The creature is resistant to gas attacks.
The creature is resistant to acid-based attacks.
The creature is resistant to frost-based attacks.
The creature is resistant to fire-based attacks.
‘resist’ can only be set in conjunction with ‘breath’ when resisting a breath that’s already set. It cannot be used when another breath or spell is set. This is a limitation of gmoria.
class humanoid { resist: fire; };
This attribute is the most complex of all of the attributes. Every attack reads like a sentence of the form ‘hit for 1|1 of normal_damage’. ‘hit’ is the attack description, ‘normal_damage’ is the kind of attack. ‘1|1’ is the hitdice for how hard this attack will hit the player. The possible attack descriptions are: ‘hits’, ‘bites’, ‘claws’, ‘stings’, ‘touches’, ‘kicks’, ‘gazes’, ‘breathes’, ‘spits’, ‘wails’, ‘embraces’, ‘crawls_on’, ‘releases_spores’, ‘begs_for_money’, ‘slimes’, ‘crushes’, ‘tramples’, ‘drools_on’, and ‘insults’.
The possible attack types are:
Causes the player’s hit-points to decrease.
Causes the player’s strength stat to decrease.
Stupifies the player.
Makes the player afraid.
Extreme heat hits the player.
Acid splashes the player.
Extreme cold hits the player.
Electricity hits the player.
Corrosive acid hits the player’s equipment.
Causes the player not to see.
Makes the player unable to move.
Makes the player lose gold pieces.
Makes the player lose an item from the inventory.
Poisons the player.
Causes the player’s dexterity stat to decrease.
Causes the player’s constitution stat to decrease.
Causes the player’s intelligence stat to decrease.
Causes the player’s wisdom stat to decrease.
Causes the player’s experience to decrease.
Wakes up nearby monsters.
Causes an item of the player’s to become less magical.
Causes the player to lose food rations.
Causes the player to lose light turns.
Causes the player’s staves to have fewer charges.
This attribute can have more than one of these values set at one time, by separating them with the comma (‘,’). Up to 4 attacks may be specified.
creature humanoid { attack: wails for 0|0 of cause_fear, touches for 22|8 of lose_exp, claws for 1|10 of lose_int; }; creature humanoid { attack: none; };
Certain attributes behave differently when derived from a class. Attributes can be overridden, merged, and negated.
Attributes like ac, level and speed only have one value, and their value is overwritten when they are derived from a class.
For example:
class warrior { ac: 2; }; class humanoid : warrior { ac: 1; };
This collection of classes results in ac being 1, not 2. The value of ac is taken from warrior as 2, and then is replaced with 1.
Other attributes like treasure, defense, move, special, spell, breath, resist and attack have more than one value, and they are merged when deriving.
For example:
class warrior { treasure: carry_obj, has_random_60; }; class humanoid : warrior { treasure: has_random_90; };
This collection of classes results in treasure being ‘carry_obj, has_random_60, has_random_90’. It is easy to merge attributes in an unexpected fashion; the user must beware. A more proper example would be:
class warrior { treasure: carry_obj, has_random_60; }; class humanoid : warrior { treasure: has_random_90, ~has_random_60; };
This collection of classes results in treasure being ‘carry_obj, has_random_90’, which is more appropriate.
It should be noted that attack attributes are also merged. For example:
class warrior { speed: 2; attack: hits for 2|10 of normal_damage; }; class humanoid : warrior { attack: hits for 1|1 of normal_damage; };
This collection of classes results in attack being: ‘hits for 2|10 of normal_damage, hits for 1|1 of normal damage’. This means the warrior sometimes hits as a warrior, and sometimes she hits as a humanoid. This probably isn’t desired. Let’s pretend there is a very good reason for deriving the ‘humanoid’ class from ‘warrior’ class, and the situation needs to be resolved. A more appropriate example would be:
class warrior { speed: 2; attack: hits for 2|10 of normal_damage; }; class humanoid : warrior { attack: none; attack: hits for 1|1 of normal_damage; };
This collection of classes result in attack being ‘hits for 1|1 of normal_damage’. The warrior class’ 2|10 attack has been overridden, and the speed attribute has been gained.
Through the practice of derivation, attributes can be merged that just don’t go together. Often there’s a way to undo it, like by using the ‘~’ operator, or the ‘none’ value. The compiler will try to complain when things don’t make sense, but it doesn’t catch all of the cases.
Let’s take this monster-definition file as an example. It describes five of the people who move around on the town level of the game.
class town_scum { level: 0; exp: 0; speed: 1; letter: "p"; defense: infra, frost, fire; ac: 1; special: picks_up, open_door; move: random_20; }; creature "Filthy Street Urchin" : town_scum { move: move_normal; sleep: 40; radius: 4; hd: 1|4; defense: evil; treasure: none; attack: begs_for_money for 0|0 of normal_damage, touches for 0|0 of steal_money; }; creature "Blubbering Idiot" : town_scum { move: move_normal; sleep: 0; radius: 6; hd: 1|2; attack: drools_on for 0|0 of normal_damage; treasure: none; }; creature "Pitiful-Looking Beggar" : town_scum { move: move_normal; sleep: 40; radius: 10; hd: 1|4; attack: begs_for_money for 0|0 of normal_damage; treasure: none; }; creature "Mangy-Looking Leper" : town_scum { move: move_normal; sleep: 50; radius: 10; hd: 1|1; attack: begs_for_money for 0|0 of normal_damage; }; creature "Singing, Happy Drunk" : town_scum { sleep: 0; radius: 10; hd: 2|3; treasure: carry_gold, has_random_60; move: random_40, random_75; attack: begs_for_money for 0|0 of normal_damage; };
Five creatures are derived from the ‘town_scum’ class. They all appear at the same level, the player is awarded with no experience points when they’re killed, they move at normal speed, they all appear as the letter ’p’ on the screen, and they all have the same ac. They can all open doors and pick up objects, and they all move somewhat randomly. All five persons are subject to being seen with infra-vision because they have warm bodies, and they are susceptible to frost and fire.
In every creature in the example, the move attribute is merged with the move value in the ‘"town_scum’ class. No attributes have negated values, and no attributes are being overridden.
When compiling this example, the compiler will warn that ‘TREASURE not defined for "Mangy-Looking Leper", line 47’. This is one of the reasons why the compiler is so useful – to point out the obvious errors. To silence this warning, add "‘treasure: none;’ to the ‘Mangy-Looking Leper’ creature block.
The resulting file that mc outputs looks like this:
/* These values should match the values defined in constant.h. */ #define MAX_CREATURES 5 #define N_MON_ATTS 4 /* The following code belongs in the file monster.c. */ /* The following data was generated by the mc program.*/ creature_type c_list[MAX_CREATURES] = { {"Blubbering Idiot" , 0x0012000AL,0x00000000L,0x2030, 0, 0, 6, 1, 11, 'p', { 1, 2}, { 3, 0, 0, 0}, 0}, {"Filthy Street Urchin" , 0x0012000AL,0x00000000L,0x2034, 0, 40, 4, 1, 11, 'p', { 1, 4}, { 1, 2, 0, 0}, 0}, {"Mangy-Looking Leper" , 0x0012000AL,0x00000000L,0x2030, 0, 50, 10, 1, 11, 'p', { 1, 1}, { 1, 0, 0, 0}, 0}, {"Pitiful-Looking Beggar" , 0x0012000AL,0x00000000L,0x2030, 0, 40, 10, 1, 11, 'p', { 1, 4}, { 1, 0, 0, 0}, 0}, {"Singing, Happy Drunk" , 0x06120038L,0x00000000L,0x2030, 0, 0, 10, 1, 11, 'p', { 2, 3}, { 1, 0, 0, 0}, 0}, }; struct m_attack_type monster_attacks[N_MONS_ATTS] = { /* 0 */{ 0, 0, 0, 0}, { 1,14, 0, 0}, {12, 5, 0, 0}, { 1,18, 0, 0} };
The entries in the c_list array happen to line up exactly with the monsters_tables.c that’s shipped with gmoria (except for the attack/damage array).
moria-mc
This is the output of the command ‘mc --help’:
Usage: moria-mc [OPTION...] FILE Generate gmoria's monsters_tables.c from FILE. -c, --consistency-check check for consistency errors -C, --constants generate constants instead of tables. -o, --outfile=FILE put generated code into FILE -?, --help give this help list --usage give a short usage message -V, --version print program version By default the monsters_tables.c file goes to the standard output unless the -o option is used. For complete documentation, visit: <http://sv.nongnu.org/p/gmoria/> Report bugs to gmoria@nym.hush.com.
FILE
is the monster-defintion file to "compile". If it is "-", then it will be read from the standard input.
mc
supports the following options:
-o
--outputfile
Put generated code into FILE. This option puts the generated monsters_tables.c file into a FILE of your choosing. If FILE is "-", then it will go to the standard output. This is the default.
-c
--consistency-check
Check for consistency errors. A loose set of rules is applied to creatures in your monster-definition file. For example, if your dragon doesn’t breathe an attack, it tells you. See the section on Consistency Checks for more information.
-C
--constants
Generate constants instead of tables.
This option makes moria-mc
generate the monsters_constant.h file instead of the monsters_tables.c file.
The following is taken from mcheck.inf, a file produced by David Grabiner and distributed with gmoria. When a line begins with ** it is a further clarification of a consistency check by Ben Asselstine. There are seven general classes that monsters fit into: Dragons, Humanoids, Undead, Animals, Demons, Quylthulgs, and Others. The consistency checks fall into these categories.
never invisible, can’t open doors, never phase, never eats others, never pick up objects, never multiply, carry objects/gold, breath weapons, cast spells, hurt by slay dragon, hurt by slay evil, can be slept, seen by infravision, young/mature 20% random movement
can open doors, never eats others, all that carry treasure pick up obj, never multiply, h/U/Y and some people don’t carry treasure, some cast spells, no breath weapons, all except some humans evil, hurt by slay evil, can be slept, seen by infravision, never random movement (except 0 level humans which are all 20% random) **harpies/nagas can’t open doors, and move randomly **invisible humanoids can’t be seen with infravision **frost-resistant humanoids can’t be seen with infravision **the carry treasure must pick-up object rule is not applied
only G invisible, all except s/Z open doors, only G/W phase, never eats others, only G picks up objects, never multiply, only s/Z do not carry objects/gold, some cast spells, no breath weapons, all evil except s/Z, hurt by slay evil, hurt by slay undead, can’t be slept, never seen by infravision, G very random movement, W 20% random movement, others never random movement **Z isn’t a kind of creature, but z is **Liches carry treasure **nether-creatures are also invisible **undead creatures who phase don’t open doors **Spirit troll is a ghost and doesn’t move randomly **the treasure rule isn’t applied
only one of a/c invisible, can’t open doors, never phase, only A eats others, never pick up objects, only a/b/F/l/r/w multiply, never carry objects or gold, never cast spells, some breath weapons, not evil, hurt by slay animal, can be slept, mammals seen by infravision, most have 20% random movement **everyone but ants and centipedes move randomly
always invisible, only B can phase, only B eats others, always pick up objects, never multiply, carry objects/gold, cast spells, only B breath weapon, all evil, hurt by slay evil, can not be slept, not seen by infravision, never random movement **quasits don’t pick stuff up **quasits move randomly
in a class by itself, almost exactly the same as demon except not evil and does not carry objects/gold, should be in class other **always moves via magic.
some can be invisible, never open doors, only X phase, only C/E/i/O eats others, only C/E/i/O pick up objects, only O/’,’ multiply, only C/i/O carry objects/gold, $ carries only gold, no breath weapons, not evil (all brainless creatures), not hurt by any special weapon, can’t be slept, never seen with infravision, brainless creatures, some drain mana/exp/etc., fire/air elementals (includes invisible stalker) move quickly, golems are animated and should never move randomly, the rest never move or move slowly/randomly if they do **$ carries objects, "only gold" isn’t represented. **not checking fire/air elemental speed as it’s not very regular **eyes can be seen with infravision **stationary creatures do not move randomly **earth based creatures can phase **fire based creatures can be seen with infravision **xorn picks up objects too **xorn moves at normal speed and not randomly **eyes and icky-things CAN be slept **Earth and Air elementals/spirits CAN open doors **Oozes may sometimes open doors
By default none of these rules are breached with the default monster data from gmoria. The consistency checker is somewhat lacking as it doesn’t look at the attack attribute, but it is a start. It is hoped that these rules aren’t too constricting to would-be game designers making new monsters for gmoria.
moria-mc
will typically be used in this fashion:
moria-mc
on the new monster-definition file
moria-mc
with the –constants option and the same monster-defintion file.
The following persons have contributed to this software:
If you find a bug in moria-mc
, please send electronic mail to
gmoria@nym.hush.com. Include the version number, which you can find by
running ‘mc --version’. Also include in your message the
output that the program produced and the output you expected.
If you have other questions, comments or suggestions about
moria-mc
, contact the author via electronic mail to
gmoria@nym.hush.com.