summaryrefslogtreecommitdiffstats
path: root/crawl-ref/docs/develop/levels/advanced.txt
blob: 4a061f00345bdc49336a74b4807a9e6e27b0cbcc (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
-----------------------------------------------
How to make levels for Dungeon Crawl Stone Soup
-----------------------------------------------

Part III:  Advanced Methods
           ================

Contents:  I. Conditionalising levels
           J. Validating levels
           K. Portal vaults
           L. Lua reference
           M. Feature names
           N. Map statistics
           O. Map generation

This document describes the advanced features of vault making. This includes
usage of lua and how to create portal vaults. Triggerables are covered in a separate document: triggerables.txt.


I.    Conditionalising levels
=============================

Crawl translates level (.des) files into Lua code chunks and runs these chunks
to produce the final level that is generated.  While you don't need to use Lua
for most levels, using Lua allows you to conditionalise or randomise levels
with greater control.

Let's take a simple example of randomisation:

NAME: random_test
# Put it on D:1 so it's easy to test.
PLACE: D:1
ORIENT: float
MAP
xxxxxxxxxxxxxxxxxxx
x........{........x
xxxAxxxxxBxxxxxCxxx
xxx.xxxxx.xxxxx.xxx
xxx@xxxxx@xxxxx@xxx
ENDMAP

Now let's say you want A, B, and C to be randomly rock or floor, but B should
be floor if both A and C are rock. Here's one way to do it (add these lines
to the map definition):

: local asolid, csolid
: if crawl.random2(2) == 0 then
:   asolid = true
:   subst("A = x")
: else
:   subst("A = .")
: end
: if crawl.random2(2) == 0 then
:   csolid = true
:   subst("C = x")
: else
:   subst("C = .")
: end
: if asolid and csolid then
:   subst("B = .")
: else
:   subst("B = .x")
: end

This code uses crawl.random2(N) which returns a number from 0 to N-1
(in this case, returns 0 or 1). So we give A a 50% chance of being
rock, and the same for C. If we made both A and C rock, we force B to
be floor, otherwise we use a subst that gives B the same 50% chance of
being rock.

You can conditionalise on various factors, such as player experience
level:

NAME: condition_002
DEPTH: 1-27
ORIENT: float
: if you.xl() > 18 then
MONS: greater mummy
: else
MONS: deep elf priest / deep elf sorcerer / deep elf demonologist
: end
MAP
xxxxxx
x1...x
x1...+
x1...x
xxxxxx
ENDMAP

Or based on where the map is being generated:

NAME: condition_003
DEPTH: Elf:*, Orc:*
ORIENT: float
: if you.branch() == "Orc" then
MONS: orc priest, orc high priest
: else
MONS: deep elf priest, deep elf high priest
: end
MAP
xxxxxx
x1...x
x2...+
x1...x
xxxxxx
ENDMAP

When conditionalising maps, remember that your Lua code executes in
two contexts:

1) An initial compilation phase before the game starts.
2) The actual mapgen phase when the dungeon builder is at work.

In context (1), you will not get useful answers from the Crawl Lua API
in general, because the game hasn't started. This is generally
ignorable (as in the case above) because the compilation phase just
checks the syntax of your Lua code. If you conditionalise your map,
however, you may run into compile failures. Take this variant, which
(incorrectly) attempts to conditionalise the map:

NAME: condition_004
DEPTH: Elf:*, Orc:*
ORIENT: float
: if you.branch() == "Orc" then
MONS: orc priest, orc high priest
MAP
xxxxxx
x1...x
x2.I.+
x1...x
xxxxxx
ENDMAP
: elseif you.branch() == "Elf" then
MONS: deep elf priest, deep elf high priest
MAP
xxxxxx
x1...x
x2.U.+
x1...x
xxxxxx
ENDMAP
: end

This map will break the compile with the cryptic message "Must define
map." (to compound the confusion, the line number for this error will
be the first line number of the map following the buggy map).

This error is because although the map is Elf or Orc only, at compile
time, the branch is *neither* Elf nor Orc, so the level-compiler
thinks you've neglected to define a map.

Lua code can detect the compile phase using crawl.game_started() which
returns true only when the player has started a game (and will return
false when the map is being initially compiled).

For more details on the available Lua API and syntax, see the Lua
reference section.


J.    Validating levels
=======================

If you have a map with lots of transforms (SUBST and SHUFFLE), and
want to guarantee that the map is sane after the transforms, you can
use a validation hook.

To take a very contrived example:

NAME: contrived_001
PLACE: D:2
ORIENT: float
TAGS: no_pool_fixup
SUBST: .=.w
SUBST: c=x.
MAP
xxxxxx
x{.+.c
x..+>x
xxxxxx
ENDMAP

This map has a chance of leaving the player stuck on the upstair
without access to the rest of the level if the two floor squares near
the doors are substituted with deep water (from the SUBST line), or
the 'c' glyph is substituted with rock. Since a cut-off vault is
uncool, you can force connectedness with the rest of the level:

validate {{ return has_exit_from_glyph('{') }}

The has_exit_from_glyph() function returns true if it is possible to
leave the vault (without digging, etc.) from the position of the {
glyph. (This takes things like the merfolk ability to swim into
account, so a merfolk character may see deep water between the stair
and door.)

The validate Lua returns false (or nil) to indicate that the map is
invalid, which will force the dungeon builder to reapply transforms
(SUBST and SHUFFLE) and validate the map again. If the map fails
validation enough times, the dungeon builder will discard the entire
level and retry (this may cause a different map to be selected,
bypassing the buggy map).

Going back to the example, if you just want to ensure that the player
can reach the > downstair, you can use:

validate {{ return glyphs_connected('{', '>') }}

NOTE: You cannot use the colon-prefixed syntax for validation Lua. If
you have a big block of code, use the multiline syntax:

validate {{
    -- This level is always cool.
    crawl.mpr("This level is guaranteed perfect!")
    return true
}}


K.   Portal Vaults
==================

Portal vaults are vaults accessed by portals in the dungeon (labyrinths
and bazaars are special cases of portal vaults). You can create custom
portal vaults in the following steps (no compilation is necessary):

* Create a new file name.des in the dat/ folder. Rules:
  The "name" should be descriptive of the vault you're adding.
  The "name" should not exceed eight letters.
  The ending must be "des".
* Add "name.des" to the list of local files in dat/clua/loadmaps.lua.
* "name.des" should contain a comment at the top, explaining flavour and
  gameplay goals of the portal vault (and perhaps additional ideas etc.)
* Define at least one vault containing the portal (see below).
* Define at least one destination map (see below).
* Add a short in-game description for the string "desc" (see below) to
  dat/descript/features.txt.

Before going into the details of portal vault creation, some words about
their uses: Portal vaults are different from branches in that they are
not guaranteed. Also, there is only one go at a portal vault - if you
leave, it's gone for good. You can apply special rules to a portal vault,
like enforcing maprot.

Portal vaults can be particulary thematic, using specialised monster
sets, fitting loot, coloured dungeon features etc. Avoid death traps; it
is no fun to enter a vault, being unable to leave and be killed outright.
In order to provide fun and reduce spoiler effects, randomise. For portal
vaults, it is desirable to have several different layouts (ideally each
of the maps has some randomisation on its own). Often, it is a good idea
to skew the map distribution: e.g. with four destination vaults, weights
like 40,30,20,10 might be more interesting than 25,25,25,25.

In order to test a portal vault, you can either use PLACE: D:2 for an
entry vault, or use the wizard mode command &L for conjuring up the entry.

Define a vault to hold the portal itself
----------------------------------------

# Bare-bones portal vault entry
NAME: portal_generic_entry
TAGS: allow_dup
ORIENT: float
MARKER: O = lua:one_way_stair { desc = "A portal to places unknown", \
                                dst = "generic_portal" }
KFEAT: O = enter_portal_vault
MAP
O
ENDMAP

Portal entries must contain a portal vault entry (enter_portal_vault).
This feature must always have a marker that provides the portal with a
description ("A portal to places unknown") and a destination
("generic_portal").

In case you want to make sure that the portal vault entry is only used
once, you add a TAGS: uniq_BAR line. It should be noted that the label
BAR may *not* end in _entry (otherwise the level builder assumes that
the vault is a branch entry).

If you want the place name displayed while in the vault to be different
than the destination name, then you can give one_way_stair() a "dstname"
parameter.  If you want the place origin for items in a character
dump to be different than the default you can give one_way_stair a
"dstorigin" parameter (i.e., dstname = "garden", dstorigin = "in the gardens").
If you want the place name abbreviation used when displaying notes to be
different than than the default you can use the "dstname_abbrev" parameter.

You can dynamically change the origin string using the lua function
dgn.set_level_type_origin(), and dynamically change the place name
abbreviation with dgn.set_set_level_name_abbrev().

Known portal vault entries will be displayed on the overmap.  By default
the name shown on the overmap will be the "dstname" parameter, or if
that isn't present the "dst" paremeter.  It can be set to something else
with the "overmap" parameter.  A note can be made to accompany the
portal's position on the overmap with the "overmap_note" parameter.

Bones files for characters killed in the portal vault will normally
use an extension derived from the first three letters of the 'dst'
property. You can override this by setting the 'dstext' property to
your preferred extension.

This will produce a portal, but attempting to use it will trigger an
ASSERT since there's no map for the destination. So we create a
destination map like so:

Define a destination map
------------------------

NAME: portal_generic_generic
# Tag must match dst value of portal in entry.
TAGS: generic_portal allow_dup
ORIENT: encompass
MONS: ancient lich
KFEAT: > = exit_portal_vault
MAP
xxxxxxxxxxx
x111111111x
x1A111111>x
x111111111x
xxxxxxxxxxx
ENDMAP

Note that the entry point into the map will be a stone arch. You must
provide an exit to the dungeon explicitly (KFEAT: > = exit_portal_vault)
or the player will not be able to leave.

Stairs will not work right in portal vaults, do not use them.

You can use multiple maps with the destination tag (generic_portal),
and the dungeon builder will pick one at random.

The MARKER parameters
---------------------

The lines
        MARKER: O = lua:one_way_stair { desc = "A portal to places unknown", \
                                        dst = "generic_portal" }
        KFEAT: O = enter_portal_vault
ensure that an 'O' glyph will be turned into a portal. Upon leaving the portal
vault, you will be placed on its entry which has been turned into a floor. You
can turn it into something different (usually an empty stone arch), by adding
        floor = 'stone_arch'
to the lua:one_way_stair parameters.

Note that the desc string is what you will see upon examining the portal.
The dst string is used for Crawl's right hand stat area; it will show
  Place: generic portal
in the above example. Here is a lost of the parameters that can be used
within one_way_stair (taken from icecave.des):
        desc = "A frozen archway",    # description of the portal before entry
        dst = "ice_cave",             # label used for maps and entry vaults
        dstname = "Ice Cave",         # used for PLACE: on the main screen
        dstname_abbrev = "IceCv",     # used in the notes
        dstorigin = "in an ice cave", # appendix for items picked up there
        overmap = "frozen archway",   # used on the overmap (X)
        floor = "stone_arch"          # feature left after escaping the portal

The dst string is also used to link the destination maps to the entry maps.
In case dstname is missing, dst will be used.

You can replace lua:one_way_stair by lua:timed_marker in order to make timed
portal vaults (which will disappear after some time). bazaar.des and lab.des
contain examples. For timed portals, you may want to add messages to the file
dat/clua/lm_tmsg.lua.

Using lua functions as shortcuts
--------------------------------

If you are making several entry and destination vaults, you will note a
lot of duplicated header statements. This can be lessened using lua.
Define a lua block right at the top (after your comments) as follows:

{{
function generic_portal(e)
  e.marker([[O = lua:one_way_stair { desc = "A portal to places unknown",
                                     dst = "generic_portal",
                                     floor = "stone_arch" }]])
  e.kfeat("O = enter_portal_vault")
  e.colour("O = magenta")
end
}}

Instead of the MARKER and KFEAT lines introduced above you now just use
  :generic_portal(_G)
and the resulting portal glyphs will even be magenta!

Defining a random monster set
-----------------------------

Portal vaults require a defined random monster set to make the Shadow
Creatures spell work. This is done by calling dgn.set_random_mon_list()
manually. Here's an example from ice_cave_small_02 in icecave.des:
  : dgn.set_random_mon_list("ice beast w:90 / ice dragon / nothing")
You can use "nothing" to have the spell fail sometimes.

If you are using the same random monster list in several destination maps,
you can define a lua block and call it from the destination map definition.
This example is from sewer.des:

{{
function sewer_random_monster_list(e)
  e.set_random_mon_list("giant bat w:20 / giant newt w:20 / small snake / \
                         ooze / worm / snake / giant mosquito w:15")
end
}}

You can then use this line in the map definition to execute the lua block:
  : sewer_random_monster_list(_G)

You can also set env.spawn_random_rate() to have monsters generated from the
list during play.

Milestones for portal vaults
----------------------------

This example is from icecave.des, defined in the lua preludes:

{{
function ice_cave_milestone(e)
  crawl.mark_milestone("br.enter", "entered an Ice Cave.")
end
}}

The function is called from each of the destination map definitions:

: ice_cave_milestone(_G)

This marks down entering the portal vault in the milestones. When the portal
is entered, the destination map is chosen and the call to crawl.mark_milestone
is executed along with the rest of the map definition.

Adding milestones in a .des does have the slight catch of creating multiple
milestones if the map fails validation for some reason, so it's best used
in maps that will never fail validation.


L.    Lua reference
===================

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 an
anonymous function (this is called a Lua chunk):

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

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

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/clua/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 {{ }} 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/mini.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.

* 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.

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.

* 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"}

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 - global game state
---------------------------

The "crawl" module provides functions that describe the game state or
provide utility methods.

mpr, mesclr, random2, coinflip, one_chance_in, redraw_screen,
input_line, c_input_line, getch, kbhit, flush_input, sendkeys,
playsound, runmacro, bindkey, setopt, msgch_num, msgch_name, regex,
message_filter, trim, split, game_started, err_trace, args,
mark_milestone


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


M.   Feature Names
==================

These are the feature names usable in MARKER declarations:

unseen, rock_wall, stone_wall, closed_door, metal_wall, secret_door,
green_crystal_wall, orcish_idol, wax_wall, permarock_wall,
silver_statue, granite_statue, orange_crystal_statue,
statue_reserved_1, statue_reserved_2, lava, deep_water, shallow_water,
water_stuck, floor, exit_hell, enter_hell, open_door, trap_mechanical,
trap_magical, trap_iii, undiscovered_trap, enter_shop,
enter_labyrinth, stone_stairs_down_i, stone_stairs_down_ii,
stone_stairs_down_iii, escape_hatch_down, stone_stairs_up_i,
stone_stairs_up_ii, stone_stairs_up_iii, escape_hatch_up, enter_dis,
enter_gehenna, enter_cocytus, enter_tartarus, enter_abyss, exit_abyss,
stone_arch, enter_pandemonium, exit_pandemonium, transit_pandemonium,
builder_special_wall, builder_special_floor, enter_orcish_mines,
enter_hive, enter_lair, enter_slime_pits, enter_vaults, enter_crypt,
enter_hall_of_blades, enter_zot, enter_temple, enter_snake_pit,
enter_elven_halls, enter_tomb, enter_swamp, enter_shoals,
enter_reserved_2, enter_reserved_3, enter_reserved_4,
return_from_orcish_mines, return_from_hive, return_from_lair,
return_from_slime_pits, return_from_vaults, return_from_crypt,
return_from_hall_of_blades, return_from_zot, return_from_temple,
return_from_snake_pit, return_from_elven_halls, return_from_tomb,
return_from_swamp, return_from_shoals, return_reserved_2,
return_reserved_3, return_reserved_4, enter_portal_vault,
exit_portal_vault, altar_zin, altar_shining_one, altar_kikubaaqudgha,
altar_yredelemnul, altar_xom, altar_vehumet, altar_okawaru,
altar_makhleb, altar_sif_muna, altar_trog, altar_nemelex_xobeh,
altar_elyvilon, altar_lugonu, altar_beogh, fountain_blue,
fountain_sparkling, fountain_blood, dry_fountain_blue,
dry_fountain_sparkling, dry_fountain_blood, permadry_fountain


N.   Map Statistics
===================

Full-debug Crawl builds (this does not include normal Crawl builds
that have wizard-mode - you must build Crawl with "make debug", not
"make wizard") can produce map generation statistics. To generate
statistics, run crawl from the command-line as:

crawl -mapstat

This will generate 100 Crawl dungeons and report on the maps used in a
file named "mapgen.log" in the working directory.

You can change the number of dungeons to generate:

crawl -mapstat 10

Will generate 10 dungeons. If you merely want statistics on the
probabilities of the random map on each level, use:

crawl -mapstat 1


O.   Map Generation
===================

Full-debug Crawl builds (see above for more information) include a test for
generating specific vaults and outputting a copy of the map to a text file for
inspection (you can also define the macro DEBUG_TESTS on an ordinary debug
build, like "make EXTERNAL_DEFINES=-DDEBUG_TESTS wizard"). This is most useful
for portal and other encompass vaults which use randomisation heavily.

To use the test, you must edit source/test/vault_generation.lua. Fill in the
following variables:

 * map_to_test: The exact name of the vault you want to generate.
 * checks: How many times to generate the vault. Default value is 10.
 * output_to: The basic filename to output the results of generation to. This
        will have ".<iteration>" appended. For example, "volcano.map" will
        result in files named "volcano.map.1", "volcano.map.2", etc.
 * need_to_load_des: If the file is not included in one of the .des files that
        are listed in source/dat/clua/loadmaps.lua, this should be set to true,
        and the following variable should be set.
 * des_file: The name of the file to load. The file should be located in the
        source/dat folder.

Once you have saved your changes, run crawl:

 crawl -test vault_generation

Once all of the tests have been finished successfully you should find the 
relevant files in your working directory.