Lua Reference

Child Contents

How maps are processed

Under the hood, Crawl translates everything in a .des file to Lua. You don't need to know what the underlying Lua looks like to design levels, but it helps.

Crawl uses Lua 5.1 from http://www.lua.org (the site has information on the Lua language). Let's examine how Crawl converts a map definition into Lua code with an example map:

NAME: statue_in_pool
TAGS: no_rotate no_pool_fixup
 : if you.absdepth() < 7 then
MONS: plant
 : else
MONS: oklob plant
 : end
MAP
1...1
.www.
.wGw.
.www.
1...1
ENDMAP

Crawl will convert this map into the following Lua code wrapped in anonymous functions (an anonymous function that takes no parameters is called a Lua chunk):

  function mapchunk()
    map("1...1")
    map(".www.")
    map(".wGw.")
    map(".www.")
    map("1...1")
  end
 
  function main()
    tags("no_rotate")
    tags("no_pool_fixup")
    if you.absdepth() < 7 then
      mons("plant")
    else
      mons("oklob plant")
    end
  end

You'll notice that these functions are not actually anonymous, but they're named just to distinguish them.

If your level defines prelude or validation Lua code, such code is extracted into separate prelude and validation chunks. The prelude and validation chunks are empty unless specified.

Apart from the special NAME map header, every map header translates to a Lua function with the same name in lowercase. For instance, KFEAT: <xyz> is translated into kfeat(”<xyz>”).

If you have a space or comma separated list (such as TAGS, MONS, ITEM, etc.), then each space/comma separated item is passed into a separate call to the corresponding Lua function. For instance:

TAGS: no_rotate no_pool_fixup

  tags("no_rotate")
  tags("no_pool_fixup")
MONS: orc, gnoll

  mons("orc")
  mons("gnoll")

Knowing what the generated Lua looks like under the hood is useful because it allows you to extract repeated boilerplate in similar vaults into a Lua function in the .des file's prelude. For instance, if you were planning to write a whole slew of vaults featuring statues in water guarded by plants, you could extract the common code into the top of the .des file as:

{{
  -- This block has to be placed before any other vault in the .des file.
  function statue_pool_map(e)
    e.tags("no_rotate")
    e.tags("no_pool_fixup")
    if you.absdepth() < 7 then
      e.mons("plant")
    else
      e.mons("oklob plant")
    end
  end
}}
 
NAME: statue_in_pool
# Pass in the Lua environment global _G to the prelude function.
 : statue_pool_map(_G)
MAP
1...1
.www.
.wGw.
.www.
1...1
ENDMAP

You can also use arbitrary Lua directly in vault definitions, which is handy when randomizing things:

NAME: statue_in_pool
 : local plant_weight = crawl.random_range(1,10)
 : mons("plant w:" .. plant_weight ..
 :      " / oklob plant w:" .. (10 - plant_weight))
MAP
1...1
.www.
.wGw.
.www.
1...1
ENDMAP

You can check what Lua code is produced when a .des file is compiled by starting Crawl with the -dump-maps option:

 ./crawl -dump-maps -builddb 2>compiled-maps.lua

This will only produce output for .des files that are compiled, so if Crawl has already compiled all .des files it will produce no output. You can force Crawl to recompile a .des file by updating its modification time or by deleting the $SAVEDIR/des directory.

Also note that Crawl writes -dump-maps output to stderr, not stdout, hence the use of 2> for redirection.

How Lua chunks are associated with a C++ map object

A map's Lua chunk consists of calls to functions such as tags(), mons(), etc. These functions are defined in the dgn table (see the Lua API reference below), and they expect to act on an instance of Crawl's C++ mapdef object. Given:

  tags("no_rotate")

the actual Lua call needs to be:

  dgn.tags(<map>, "no_rotate")

Where <map> is the C++ map object to which the tag should be added. Since calling dgn.<foo>(<map>, <xxx>) is tedious, dat/dlua/dungeon.lua wraps the Lua chunk for the map into an environment that defines wrappers for all the functions in 'dgn' as:

  function <xyz>(...)
    dgn.<xyz>(<map>, ...)
  end

i.e. for every function <xyz> in the 'dgn' table, we define a new function <xyz> that just calls dgn.<xyz>() with the current map as the first parameter, and the other parameters as passed in. Thus Lua code that you write as:

  tags("no_rotate")

is translated to the correct

  dgn.tags(<map>, "no_rotate").

While this is done automatically for map code, if you need to call Lua code that was not defined in the scope of the map, as in the example statue_pool_map() function, you need to pass in the map environment to that function if you want it to modify the map. Thus the call to statue_pool_map looks like:

 : statue_pool_map(_G)

Steps involved in processing .des files

  • Level files are compiled into a series of Lua chunks. Each map can have one or more Lua chunks associated with it: the prelude, the body, and a validation chunk. The body is mandatory, but validation and prelude chunks are necessary only if your map needs validation or fancy selection criteria.
  • When first compiling a .des file, Crawl compiles each map's Lua chunks, then compiles and runs the prelude, body and validation immediately to verify that the Lua code is not broken. Lua errors at this stage will cause Crawl to exit with an error message (hopefully relevant). Note that the validation Lua chunk's return code is completely ignored at this stage — it is only run to check for syntax errors in the code.
  • When a new game is started, Crawl will run the Lua preludes for all maps (most maps should have no prelude — map preludes slow the game down). At this point, preludes can change the map's placement or availability.
  • When the dungeon builder selects a map (based on TAGS, DEPTH, PLACE), it re-runs the map prelude and the map body, applies transforms (SUBST, SHUFFLE) if any, then calls the map's validation Lua. If the map passes validation, the dungeon builder continues with level-generation; otherwise, it restarts from the map prelude.

The global prelude

Every .des file can have (at the start of the file) Lua code that is not associated with any specific map, but with all maps in the file. This is called the global prelude. The global prelude is run before running any other Lua code in the file, once during compilation, and once at start of game.

You can use the global prelude to define functions and set up globals that the rest of the maps in the .des file use. If you have a lot of common code, you should probably add it to dungeon.lua instead.

Syntax for using Lua in .des files

  • Colon-prefixed lines are individual Lua lines, extending to the end of the line. E.g.
     : crawl.mpr("Hello")


    Colon-prefixed lines are always in the main Lua chunk, unless they occur before any map definitions, in which case they go to the global prelude.

  • Lua blocks for the main (body) Lua
       lua {{ <code> }}

    or

       lua {{
           <code>
       }}

    The “lua” word is optional; you can also use:

       {{ <code> }}

    and

       {{
       <code>
       }}


    NOTE: Colon-prefixed lines, or lua maps blocks defined before any map's NAME: directive will add the Lua code to the global prelude.

  • Lua blocks for the prelude:
      prelude {{ <code> }}

    or

       prelude {{
           <code>
       }}
  • Lua blocks for the validate chunk:
       validate {{ <code> }}

    or

       validate {{
           <code>
       }}

Debugging Lua

Since Lua action happens in the guts of Crawl, it can be hard to tell what's going on. Lua debugging involves the time-honoured method of peppering your code with print statements:

  • Use error() or print() for compile-time work (i.e. when Crawl reads the .des file). Note that print() just writes to the terminal and keeps going, while error() forces Crawl to exit immediately (at compile time; errors during level-generation are handled differently).
  • Use crawl.mpr() for output when the game has started (at level-generation time).

It's very important that your finished level never croaks during level-generation. A Lua error at this stage is considered a validation failure.

Special dungeon-related Lua marker properties

There are several properties a Lua marker can have which will affect the dungeon cell which they are on:

  • connected_exclude: Consider the cell to be separate from neighboring cells with identical or similar features. Currently only useful for preventing adjacent doors from grouping together into a gate, forcing them to open and close as separate doors. See the Evil Zoo (minivault_9) in dat/variable/mini_monsters.des for an example.
  • door_description_prefix: A string to prepend to the description of any door the marker is on. This should be used for doors rather than the feature_description property since it elemintates the need to track if the door is opened or closed, plus it will have no effect on secret doors which have yet to be detected.
  • door_description_suffix: A string to append to the description of any door the marker is on. This should be used for doors rather than the feature_description property since it elemintates the need to track if the door is opened or closed, plus it will have no effect on secret doors which have yet to be detected.
  • door_open_prompt: If placed on top of a door, the use will be prompted before opening the door, with the value of the property used as the prompt string.
  • door_description_adjective: Overwrite the adjective for the door. Not currently used.
  • door_description_noun: Overwrite the noun used by the door. Replaces, for instance, “door” with “doorway”.
  • door_description_veto: Vetoes the use of “open”, “closed” and “detected secret” when applying adjectives to door descriptions.
  • door_berserk_verb_open: Replace the verb used for opening the door while berserk. Should include ”%s%s”, as it is printed as a formatted string.
  • door_berserk_adjective: Replaces the adjective “with a bang” when the player is not silenced while opening a door.
  • door_noisy_verb_open: Replaces “opens with a creak”. Also requires ”%s%s” as it is a formatted string.
  • door_airborne_verb_open: Replaces “reach down and open”, also requires ”%s%s”.
  • door_open_verb: Replaces “You open”. Also requires ”%s%s”. All of the above “open” have “close” counterparts which are used when closing a door.
  • feature_description: What to use as the short description of the cell's feature.
  • feature_description_long: What to use as the long description of the cell's feature.
  • stop_explore: If set to anything, and placed on a cell with a statue or orcish idol, will cause auto-explore to stop with the message Found <whatever>.
  • stop_explore_msg: Like stop_explore, but when auto-explore is stopped the content of the property will be printed out as a message.
  • veto_disintegrate: If this property is set to “veto” then the cell will be immune to disintegration.
  • veto_fragmentation: If this proprety is set to “veto” then the cell will be unaffected by fragmentation (Lee's Rapid Deconstruction spell).
  • veto_shatter: If this property is set to “veto” then the cell will be unaffected by the Shatter spell.
  • veto_fire: If this property is set to “veto” then the cell will be unaffected by any fire spells.

Special monster-related Lua marker properties

Using the MonPropsMarker allows you to permanantly alter or mark a monster that the marker is placed upon. The options currently available are:

  • description: If this property is set, the monster's full description (accessed via the 'xv' command) will be set to whatever string you pass it.
  • quote: Setting this property to a string will set the monster's quote.
  • monster_dies_lua_key: If this property is set to a function, that function will be executed upon the monster's death.
  • shout_func: If this property is set to a function, that function will be called if the monster shouts, and the string returned by the function will be used as the text of the message. can return ”__NONE” to make the monster not shout, or ”__NEXT” to proceed as if there was no speech_func property.
  • speech_func: If this property is set to a function, that function will be called if the monster speaks, and the string returned by the function will be used as the text of the message. can return ”__NONE” to make the monster not talk, or ”__NEXT” to proceed as if there was no speech_func property.
  • speech_key: This will override the initial key searched for in the speech database. Setting this to “Edmund”, for example, will give the relevant monster Edmund's speech.
  • speech_prefix: This allows a single prefix to be added to the prefixes list. These prefixes can be used to adjust monster speech dependant on circumstances.

An example of MonPropsMarker to replace the description and quote of a monster:

  MARKER: 8 = lua:MonPropsMarker:new {description="What a horrible sight!\n", \
                                      quote='"They were filled with fear!'\n"}

Note that all of the speech related properties will be reset if the monster polymorphs.

Lua API reference

a. The Map. b. Global game state. c. Character information.

Lua API - the Map

Lua functions dealing with the map are mostly grouped under the “dgn” module. For convenience, .des file Lua chunks are run in an environment such that function calls written as:

fn(x, y, ...)

are translated to

dgn.fn(map, x, y, ...)

where “map” is the reference to the map that the currently executing Lua chunk belongs to. This is only for Lua chunks that belong to a map, Lua code in the global prelude does not get this treatment (because the global prelude is not associated with any map).

Functions in the dgn module:

default_depth, name, depth, place, tags, tags_remove, chance, weight, orient, shuffle, shuffle_remove, subst, subst_remove, map, mons, item, kfeat, kitem, kmons, grid, points_connected, gly_point, gly_points, original_map, glyphs_connected, orig_glyphs_connected, orig_gly_point, orig_gly_points, load_des_file, feature_number, feature_name, dgn_event_type, register_listener, remove_listener, remove_marker, num_matching_markers, feature_desc, feature_desc_at, item_from_index, mons_from_index, change_level_flags, change_branch_flags, set_random_mon_list

Additionally, the dgn module provides a global “mapgrd” variable that can access the current map glyphs. The top left symbol in the map can be assigned like this:

mapgrd[0][0] = 'x'

The bottom right symbol can be assigned like this:

mapgrd[width()-1][height()-1] = "."
Lua API - character information

The “you” module provides functions that describe the player character.

turn_is_over, spells, abilities, name, race, class, god, hp, mp, hunger, strength, intelligence, dexterity, xl, exp, res_poison, res_fire, res_cold, res_draining, res_shock, res_statdrain, res_mutation, res_slowing, gourmand, levitating, flying, transform, stop_activity, floor_items, where, branch, subdepth, absdepth

Lua API - colour definitions

The “colour” module provides functions for defining new colour patterns.

<code>
add_colour
</code>

Lua hooks

When the dungeon builder places a vault, it runs hooks at certain steps during vault building. These hooks are:

  1. pre_main: This hook is called just before the main chunk's Lua code is called.
  2. post_main: This hook is called just after the main Lua code is called.
  3. post_place: This hook is called after the vault has been placed, and before the dungeon builder continues with level-generation, i.e. during level-generation time.
  4. pre_epilogue: This hook is called just before the epilogue code is run. Note that by the time epilogues run, level generation is complete.
  5. post_epilogue: This hook is called after the epilogue code is run.

You may also specify hooks as “main”, “place”, “epilogue”, which will select the post_XXX hook. i.e. using “main” as the hook name is the same as writing “post_main”.

Hooks may be global, in which case they're fired for all vaults placed, or may be specific to a map. Global hooks must be defined in a Lua file, or in a .des file's global prelude.

You may use the post_place hook to take an action immediately after a vault is placed successfully. For instance, if you're designing a vault (star) that should always be accompanied by several other vaults (fan), then you might write it as:

NAME: star
PLACE: D:2
{{
  hook("post_place", function()
    -- Place ten instances of vault(s) tagged "fan"
    dgn.place_maps{tag="fan", count=10}
  end)
}}
MAP
***
***
ENDMAP

NAME: fan
# Must be allow_dup since 10 instances will be used.
TAGS: fan allow_dup
MONS: human
MAP
1
ENDMAP

Using global hooks affects every vault, so they should be used very sparingly. Global hooks must be defined in Lua files, or in .des files' global preludes (i.e. before any map definitions). As an example, here's a frivolous global hook to place a pile of gold on each floor square in vaults:

  {{
    dgn.global_hook("main",
                    function ()
                      kitem(". = gold")
                    end)
  }}

Note the use of the “main” hook in this example instead of “post_place”. “post_place” is too late to modify the vault definition, since the vault is already placed at that point.

Errors in hooks

Errors in hooks will usually be ignored by the level builder, although errors will still be displayed to the end user. The only exception is the “post_place” hook – errors in the post_place hook will cause the dungeon builder to veto its current level and retry level generation.

Logged in as: Anonymous (VIEWER)
dcss/help/maps/lua.txt · Last modified: 2011-09-09 12:24 by mumra
 
Recent changes RSS feed Donate Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki