Skip to content

Creature Definition Functions

Joachim de Groot edited this page Sep 26, 2026 · 1 revision

Every creature in Avanor is defined in Lua, under world/creatures/ for the ordinary sorts and world/uniques/ for the named ones. The engine compiles in no creature of its own: what follows is the whole of the seam between the two.

Table of Contents


Overview

A definition is a chain of calls on a builder, ended by :Register():

Monster.new("rat")
    :View("rat", 'r', xColor.xBROWN, PersonType.IT, CreatureTemplate.VERY_LOW, "rat")
    :Basic("1d30+90", "0d0+1000", "1d200+600", CreatureSize.VERY_SMALL, "5d4")
    :Body("", 0)
    :AI(XStandardAI.COWARD + XStandardAI.RANDOM_MOVE + XStandardAI.ALLOW_PACK)
    :Stats("St 1d2 Dx 2d4 To 1d2 Le 1d1 Wi 1d1 Ma 1d1 Pe 2d4 Ch 1d1")
    :Resist{ fire = "5d5-50", cold = "2d10" }
    :Combat("0d0", "1d2")
    :Main("1d3", "0d0", "1d3", "0d0")
    :Description("Scuttling about in the shadows, rats can be found most "
        .. "places in the valley.")
    :CorpseEffect(CorpseEffectType.VOMIT, 0)
    :CorpseTaste("emetic")
    :Register()

Nothing is ordered: the calls may come in any sequence. Only :Register() must come last, and a definition that omits it defines nothing at all.

Ids are strings. A creature's id, its class, its spells and the item types it carries are all free-form strings, not engine enums - a creature class is whatever world/creature_classes.lua declares, a spell whatever world/spells.lua does. The enums that remain (PersonType, CreatureSize, CreatureTemplate, XSkill, XStandardAI, CorpseEffectType, ItemKind, xColor) are engine concepts rather than content.


Function Reference

Identity

Monster.new(id) / Monster.new(id, base)

Starts a definition. With a second argument it starts from a copy of that template - see Inheritance.

:View(name, view_char, color, person, level, class)

  • name - what the player reads ("huge bat"). Not the id
  • view_char - the map glyph, as a Lua character ('b')
  • color - an xColor value
  • person - PersonType.IT, HE, SHE, THEY, or the NAMED_* forms for something with a proper name. This decides the pronoun and the verb: NAMED_* and THEY take an uninflected verb ("they bite"), the rest take the "s" ("it bites")
  • level - a CreatureTemplate level, below
  • class - a class id declared in world/creature_classes.lua: rat, bat, feline, canine, reptile, insect, human, orc, giant, kobold, undead, goblin, demon, humanoid, blob, other

Levels: VERY_LOW, LOW, ABOVE_LOW, AVG, ABOVE_AVG, HI, ABOVE_HI, VERY_HI, EXTREM_HI, UNIQUE, and the spans ANY, VL, LA, AH. Where a level is asked for as a filter - Settle(), the random draws - it is a ceiling, not a set: anything of that level or below may come up. This is why the ladders in world/locations/ stop at HI and never reach UNIQUE.

:Description(text)

What the player reads on looking at one. Written as a wrapped Lua string:

    :Description("With a wing span up to 10 feet, these bats can carry "
        .. "away much larger prey than their smaller cousins.")

:Unique()

There is only ever one. A unique is never settled by a generator and never drawn at random, so it appears only where a script puts it.

:Register()

Files the finished template. Nothing exists until this is called.


Figures

:Basic(speed, move_energy, attack_energy, size, weight)

All four of the first arguments are dice strings; size is a CreatureSize (VERY_SMALL, SMALL, NORMAL, LARGE, VERY_LARGE).

:Stats(stat_string)

The eight attributes, as dice, named in pairs:

    :Stats("St 1d2 Dx 2d4 To 1d2 Le 1d1 Wi 1d1 Ma 1d1 Pe 2d4 Ch 1d1")

Strength, Dx dexterity, Toughness, Learning, Willpower, Mana, Perception, Charisma. The generator merges: a stat the string does not name keeps whatever it had, which is what makes inheritance useful.

:Main(dv, pv, hp, pp)

Defence value, protection value, hit points and power points, as dice.

:Combat(hit_dice, damage_dice)

The to-hit and damage of its bare attack.


Body and kit

:Body(body_string, equip_probability)

Which body parts it has, space-separated, and how likely each is to be given something to wear when one is made.

    :Body("", 0)                                    -- a rat: no parts at all
    :Body("head neck body hand hand", 5)            -- a skeleton, rarely kitted
    :Body("head neck body cloak hand hand ring ring gloves boots", 100)

Parts: head, neck, body, cloak, hand, ring, gloves, boots, light_source, tool, missile_weapon, missile. Repeat one to have two of it. A creature with no hand cannot hold a weapon, and - since doors are worked by hand - cannot open a door either.

equip_probability is a percentage per slot, applied when the creature is created.

:Equip(item_kind, item_id, probability)

Gives it a particular thing, with probability per cent.

    :Equip(ItemKind.WEAPON, "short_sword", 100)
    :Equip(ItemKind.BODY, "robe", 100)

item_id is a content id from world/items/, not an enum.

:EquipCount(item_kind, count, probability)

The same, but count random items of that kind rather than a named one.


Behaviour

:AI(flags)

XStandardAI flags (see the AI flags documentation for the full list and their description) added together:

    :AI(XStandardAI.COWARD + XStandardAI.RANDOM_MOVE + XStandardAI.ALLOW_PACK)

:AI() assigns, it does not add. On an inherited template it replaces the parent's flags outright - unlike :LearnSkill(), :Equip(), :Always() and :Never(), which append. To keep a parent's behaviour and add to it, repeat the parent's flags.

:LearnSkill(skill, level)

    :LearnSkill(XSkill.HEALING, 10)
    :LearnSkill(XSkill.FINDWEAKNESS, XSkill.MAX_LEVEL)

:LearnSpell(spell_id)

A spell id from world/spells.lua:

    :LearnSpell("fire_bolt")

:Melee(brand, probability)

Its bite or sting carries a brand, probability per cent of the time. The brand is an id from world/brands.lua:

    :Melee("poison", 40)
    :Melee("disease", 30)

:MeleeExtra(extended_attack, probability)

An attack that does something beyond damage. Bound but not usable from content as it stands: its argument is the C++ EXTENDED_ATTACK enum (EA_NONE, EA_SPAWN), which is not published to Lua under any name, so there is nothing to pass but a bare integer. No creature in world/ uses it.


Resistance

:Resist{ ... }

A table, keyed by resistance id. A dice string sets a percentage; true sets a flag.

    :Resist{ fire = "5d5-50", cold = "2d10" }
    :Resist{ poison = "1d1+99", see_invisible = true }

Resistance is a percentage reduction (dmg - dmg * R / 100), so it reduces and only prevents outright at 100. The ids are whatever world/resistances.lua declares.

:Always(resist) / :Never(resist)

Fix a flag-like resistance on or off regardless of what is rolled:

    :Never("invisible")
    :Always("see_invisible")

Both append, so an inherited template keeps the parent's and adds its own.


The corpse

:Corpse(rotting_time)

How long the body lasts. One argument - what the corpse tastes of is :CorpseTaste().

:CorpseTaste(taste)

"best", "good", "normal", "bad", "aversive" or "emetic", as declared in world/tastes.lua.

:CorpseEffect(effect, value)

What eating it does: CorpseEffectType.STOMACH, VOMIT or SATIATION.

:CorpseStat(stat, value) / :CorpseResist(resist, value) / :CorpseModifier(modifier, value)

What eating it grants - an attribute, a resistance, or a modifier laid on the eater.

    :CorpseStat("St", 1)
    :CorpseResist("poison", 1)
    :CorpseModifier("disease", 50)

value for a modifier is the chance in per cent of taking it.


Inheritance

Monster.new(id, base) copies the whole of the base template, then applies whatever the new definition says:

Monster.new("huge_bat", "bat")
    :View("huge bat", 'b', xColor.xLIGHTGRAY, PersonType.IT, CreatureTemplate.VERY_LOW, "bat")
    :Basic("1d30+150", "0d0+1000", "1d200+700", CreatureSize.VERY_SMALL, "10d4")
    :Stats("St 1d4 Dx 1d6 To 1d2 Pe 4d10")
    :Resist{ see_invisible = true }
    :Combat("1d2", "1d4")
    :Main("1d4", "1d1", "1d6", "0d0")
    :Description("With a wing span up to 10 feet ...")
    :Register()

This one says nothing about :AI() or :Body() and so keeps the bat's, and its :Stats() names four attributes, leaving the other four as the bat had them.

Which calls replace and which append:

Replace Append
View, Basic, Body, AI, Combat, Main, Description, Corpse, CorpseTaste LearnSkill, LearnSpell, Equip, EquipCount, Melee, Always, Never, CorpseStat, CorpseResist, CorpseModifier

:Stats() and :Resist{} are the odd ones: they merge, field by field.


Worked Examples

A plain creature

See the rat at the top of this document.

An inherited one

See the huge bat above.

A unique

Monster.new("highpriest")
    :View("Aphilius, the high priest of Avanor", 'p', xColor.xWHITE,
          PersonType.NAMED_HE, CreatureTemplate.UNIQUE, "humanoid")
    :Basic("1d10+100", "0d0+800", "0d0+800", CreatureSize.NORMAL, "1d200+1200")
    :Body("head neck body cloak hand hand ring ring gloves boots light_source tool missile_weapon missile", 100)
    :Never("invisible")
    :Always("see_invisible")
    :AI(XStandardAI.RANDOM_MOVE + XStandardAI.ALLOW_PICK_UP
        + XStandardAI.ALLOW_WEAR_ITEM + XStandardAI.COWARD + XStandardAI.PEACEFUL)
    :Stats("St 1d8+10 Dx 1d8+15 To 1d8+10 Le 1d5+25 Wi 1d5+25 Ma 1d5+10 Pe 5d6 Ch 6d5")
    :Combat("1d3", "1d2")
    :Main("1d4", "1d2", "1d5+30", "1d5+10")
    :Description("This compassionate soul gives his time and devotion to "
        .. "maintaining the temple.")
    :LearnSkill(XSkill.HEALING, XSkill.MAX_LEVEL)
    :LearnSpell("heal")
    :Equip(ItemKind.BODY, "robe", 100)
    :Unique()
    :Register()

A unique is only a template: what makes it this character is the script that places it and the event handler attached there - see Level Definition Functions.


Traps and Gotchas

Every dice string is rolled at load time. XDice::Setup() ends by throwing the dice, so adding or removing any call that carries a dice string consumes a different amount of randomness and shifts every seed after it. Two builds of the same seed are only comparable if the definitions between them are identical - which is why "byte-identical output" is not a safe way to prove a definition change was inert.

:AI() is the one that replaces. Inheriting a creature and giving it one extra flag silently drops everything the parent had.

A creature with no hand cannot open a door. This is what keeps animals out of buildings, and what let the tomb skeletons out once monsters learned to work doors.

The level is a ceiling wherever it is used as a filter. A dungeon that settles at UNIQUE will happily settle a second Todin.


See Also

  • world/creature_classes.lua - the classes, and what :Slain() and :NoCorpse() say about a whole sort at once
  • world/brands.lua, world/spells.lua, world/tastes.lua - the ids :Melee(), :LearnSpell() and :CorpseTaste() name