-
Notifications
You must be signed in to change notification settings - Fork 1
Creature Definition Functions
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.
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.
Starts a definition. With a second argument it starts from a copy of that template - see Inheritance.
-
name- what the player reads ("huge bat"). Not the id -
view_char- the map glyph, as a Lua character ('b') -
color- anxColorvalue -
person-PersonType.IT,HE,SHE,THEY, or theNAMED_*forms for something with a proper name. This decides the pronoun and the verb:NAMED_*andTHEYtake an uninflected verb ("they bite"), the rest take the "s" ("it bites") -
level- aCreatureTemplatelevel, below -
class- a class id declared inworld/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.
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.")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.
Files the finished template. Nothing exists until this is called.
All four of the first arguments are dice strings; size is a
CreatureSize (VERY_SMALL, SMALL, NORMAL, LARGE, VERY_LARGE).
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.
Defence value, protection value, hit points and power points, as dice.
The to-hit and damage of its bare attack.
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.
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.
The same, but count random items of that kind rather than a named one.
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(XSkill.HEALING, 10)
:LearnSkill(XSkill.FINDWEAKNESS, XSkill.MAX_LEVEL)A spell id from world/spells.lua:
:LearnSpell("fire_bolt")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)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.
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.
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.
How long the body lasts. One argument - what the corpse tastes of is
:CorpseTaste().
"best", "good", "normal", "bad", "aversive" or "emetic", as
declared in world/tastes.lua.
What eating it does: CorpseEffectType.STOMACH, VOMIT or SATIATION.
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.
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.
See the rat at the top of this document.
See the huge bat above.
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.
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.
-
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