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 933 934 935 936 937 938 939 940 941 942 943 944 945 946 947 948 949 950 951 952 953 954 955 956 957 958 959 960 961 962 963 964 965 966 967 968 969 970 971 972 973 974 975 976 977 978 979 980 981 982 983 984 985 986 987 988 989 990 991 992 993 994 995 996 997 998 999 1000 1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014 1015 1016 1017 1018 1019 1020 1021 1022 1023 1024 1025 1026 1027 1028 1029 1030 1031 1032
|
-----------------------------------------------
How to make levels for Dungeon Crawl Stone Soup
-----------------------------------------------
Part III: Advanced Methods
================
Contents: I. Conditionalising levels
J. Validating levels
K. Vetoing levels
L. Abyss vaults
M. Portal vaults
N. Lua reference
O. Lua hooks
P. Map statistics
Q. 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.
There are a number of caveats that go with phase 1. You can detect this phase
by using the `is_validating()` call or an `e.is_validating()` call where `e` is
the current map (`_G`).
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).
In phase (1), you should avoid conditionals that impact tags randomly, and if
you are using any form of tag randomization, wrap it in a validating check. For
example:
: if not is_validating() and crawl.coinflip() then
TAGS: no_pool_fixup
: end
See `rng_guidelines.md` for more details on this particular caveat.
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. Vetoing Levels
===================
Similarly to validating a level post-placement, it is possible to veto
placement of a map before it is even considered. This can be especially useful
if, in the example of due_tower_of_silence, you don't want a unique-specific
map to be placed if that unique has already been placed by another map.
Veto blocks, however, use the inverse logic to validate blocks: if a veto block
returns true, the map will not be placed, otherwise placement will continue as
normal. For example:
veto {{
if you.uniques("Jory") then
return true
else
return false
end
}}
In the event that a map that has been chosen via a PLACE tag is vetoed, and
there are no other suitable maps to replace it as a primary vault, then the
dungeon builder will fail to generate a level for that place. This will cause
the game to close.
Do not use veto chunks with vaults tagged with PLACE unless you are absolutely
certain this will not happen.
L. Abyss Vaults
=================
Abyssal vaults have more limitations than vaults in the regular
dungeon, because the Abyss is constantly shifting under the player.
Normal abyss vaults can be assigned a DEPTH tag as with other branches; it
picks vaults tagged "abyss_rune" for abyssal rune vaults and vaults tagged
"abyss_exit" for abyssal stairs and exits.
When designing abyssal vaults, keep in mind that:
a. Abyssal vaults must be small: no larger than 28x23. 23x23 or
smaller vaults are best so that they can be rotated and fit in
anywhere on the level.
b. Unique vaults (vaults without the "allow_dup" tag) are tracked
separately for the abyss, so if a map's DEPTH spec allows it to
place both inside and outside of the abyss, it may appear up to
two times even if unique. The same applies to unique tags.
c. The player may never get a chance to explore unique abyss vaults.
The player may be teleported away to a different area of the abyss,
or never notice your vault before it shifts away.
Unique vaults that the player never sees will be reused by the
abyss builder, but it is still possible for the player to see an
outer wall of your vault and not notice it, or for the player to
try to explore it and be teleported away by the abyss before they
can finish exploring it.
Once any square of a unique vault has been seen by the player in
the abyss, that vault cannot be reused within the abyss.
d. Timers and other listeners in an abyssal vault may be discarded at
any time when the abyss shifts and the vault is destroyed. You can
still use timers to modify your vaults, but be aware that the timer
may be removed at any time as the abyss shifts away from your
vault.
M. Portal Vaults
==================
Portal vaults are branches accessed by portals in the dungeon. You can
create custom portal vaults in the following steps (note that compilation
is necessary), assuming that you want to call your new portal "NewPortal"
for whatever reason:
* Create a new file newportal.des in the dat/portals/ folder.
* "newportal.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 branch to dat/descript/branches.txt
* Add code for the branch in branch-data.h, like so:
{ BRANCH_MYPORTAL, NUM_BRANCHES, -1, -1, 1, 14,
BFLAG_NO_XLEV_TRAVEL | BFLAG_NO_ITEMS,
DNGN_ENTER_MYPORTAL, DNGN_EXIT_MYPORTAL,
"NewPortal", "an awesome portal place", "NewPortal",
nullptr,
BLUE, BLUE,
'7', 0 },
* Add code for the entrances and exits to feature-data.h, like so:
PORTAL_ENTRANCE(DNGN_ENTER_MYPORTAL, "awesome portal in", "enter_newportal", BLUE),
PORTAL_EXIT(DNGN_EXIT_MYPORTAL, "awesome portal out", "exit_newportal", BLUE),
* Add BRANCH_MYPORTAL, DNGN_ENTER_MYPORTAL, and DNGN_EXIT_MYPORTAL to enum.h
in the appropriate places (copy and paste from another portal vault is fine
for this).
Before going into the details of portal vault creation, some words about
their uses: Portal vaults are different from other 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
KFEAT: O = enter_newportal # name defined in feature-data.h
MAP
O
ENDMAP
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).
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
DEPTH: NewPortal # name defined in branch-data.h
TAGS: allow_dup
ORIENT: encompass
MONS: ancient lich
KFEAT: > = exit_newportal # name defined in feature-data.h
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_newportal)
or the player will not be able to leave. Simple stairs will not work right
as exits.
You can use multiple maps with DEPTH: NEWPORTAL, and the dungeon builder
will pick one at random.
Defining a random monster set
-----------------------------
Portal vaults may use 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("bat w:20 / frilled lizard w:20 / small snake / \
ooze / worm / snake / vampire 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.
You can also edit mon-pick-data.h, but that is more complicated, and not
necessary if you have a random monster list set in the vault.
N. 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
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 {{ }} 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 neighbouring
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. For a more powerful version, you can
use set_feature_name() instead.
* door_description_suffix: A string to append to the description of
any door the marker is on.
* 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 "runed"
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 property 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 permanently 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, 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_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.
add_colour
O. 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.
P. Map Statistics
===================
Full-debug Crawl builds (i.e., ones built with "make debug") 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
You can instead limit the levels to operate on by providing a comma
separated list of level ranges, with same syntax as in DEPTH: stanzas:
crawl -mapstat D:15,Zot,!Zot:5
Mapstat tends to take large amounts of time, so remember you can have
optimized debug builds by 'make debug CFOPTIMIZE="-Ofast"' if you're not
after backtraces (mapstat is quite good for finding map generation crashes).
CFOPTIMIZE is also a good place for inserting -pg into.
Q. Map Generation
===================
Full-debug Crawl builds (see above for more information) include a script that
can attempt to generate vaults either singly or in bulk, and test placement
conditions. This can be useful for finding closets, among other things,
especially in heavily randomized vaults. This script must be run under the
fake_pty utility (`make util/fake_pty` to ensure it is built.)
To see all command line options, run:
util/fake_pty ./crawl -script placement.lua --help
An example use case is:
util/fake_pty ./crawl -script placement.lua minmay_nonomino_d4 -count 10 -dump
This command tries to place the vault `minmay_nonomino_d4` 10 times, generating
an ascii output file for each placement. If any of these times generate a
placement issue, an error will be shown as part of the command output. For
example (this bug is long fixed):
Testing map 'minmay_nonomino_d4'
Failing vault output to: placement-minmay_nonomino_d4.10.txt
script error: ./scripts/placement.lua:184: Isolated area in vault minmay_nonomino_d4 (2 zones)
The indicated output file could then be expected to try to identify the
placement error. This script supports a number of other modes, and can be run
on large batches of vaults at once; see the `--help` output for more
information. This script does not handle encompass vaults.
|