diff options
author | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2007-06-28 12:04:38 +0000 |
---|---|---|
committer | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2007-06-28 12:04:38 +0000 |
commit | d1968a5ba426ba5d202ff2824115b568de3f5f3f (patch) | |
tree | 04495c853b89bb901659bf1959b97ae835e79816 /crawl-ref | |
parent | 26f8579e71cadfe6d757a1a8c979a51eba6305df (diff) | |
download | crawl-ref-d1968a5ba426ba5d202ff2824115b568de3f5f3f.tar.gz crawl-ref-d1968a5ba426ba5d202ff2824115b568de3f5f3f.zip |
Updated level-design.txt with a basic overview of the Lua possibilities.
Fixed some inconsistencies in the handling of Lua errors.
Tweaked the lexer to allow spaces before Lua chunk prefixes.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@1676 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'crawl-ref')
-rw-r--r-- | crawl-ref/docs/level-design.txt | 391 | ||||
-rw-r--r-- | crawl-ref/source/clua.cc | 27 | ||||
-rw-r--r-- | crawl-ref/source/dungeon.cc | 2 | ||||
-rw-r--r-- | crawl-ref/source/mapdef.cc | 31 | ||||
-rw-r--r-- | crawl-ref/source/mapdef.h | 4 | ||||
-rw-r--r-- | crawl-ref/source/maps.cc | 9 | ||||
-rw-r--r-- | crawl-ref/source/util/levcomp.lpp | 10 |
7 files changed, 433 insertions, 41 deletions
diff --git a/crawl-ref/docs/level-design.txt b/crawl-ref/docs/level-design.txt index c62061e3eb..1560769318 100644 --- a/crawl-ref/docs/level-design.txt +++ b/crawl-ref/docs/level-design.txt @@ -5,8 +5,10 @@ Contents: A. Introduction B. Sample map C. Map symbols D. Header information - E. Hints for level makers - + E. Conditionalising levels + F. Validating levels + G. Hints for level makers + H. Lua reference A. Introduction ----------------- @@ -89,15 +91,9 @@ minivault. B. Sample Map --------------- -Before explaining the many technical details of the level syntax, we give -a fictional temple entry so that you can the general map structure by way -of example. This is a _bad_ entry - do not recycle it! - -First of all, each and every map consists of a name, a header and the actual -map itself (order is often not important but try to stick to this one). - -Note that lines starting with # are comments. The keywords are explained -very briefly after the map and in detail in the following sections. +Before going into the technical details of the level-file syntax, +let's look at an example - a branch entry for the Ecumenical Temple - +to see what a map definition looks like. # name below: NAME: a_useless_temple_entry_02 @@ -119,10 +115,18 @@ x2www1x xx1.1xx ENDMAP +Every map consists of a name, a header and the actual map (the order +is not important as long as the name comes first, but stick to this +order for consistency). + +Lines starting with # are comments. The keywords available are +explained briefly after the example map and in detail in the following +sections. + "ORIENT: float" tells the level builder that this entry can be anywhere on the level; other ORIENT: values can force a map to one edge of the level. -"CHANCE: 5" makes it appear less often (default is 10). +"CHANCE: 5" makes the map appear less often (default is 10). "TAGS: temple_entry" turns the 'O' on the map into stairs to the Temple. "FLAGS: no_rotate" forbids rotation (but mirroring is still allowed). "SHUFFLE: de" may replace all 'd' with 'e' in the map. @@ -237,6 +241,9 @@ Monsters D. Header information ----------------------- +(All declarations apart from NAME: are translated to Lua function +calls behind the scenes. See the Lua reference for more information.) + NAME: a_string Each map must have a unique name. Underscores and digits are ok. @@ -513,7 +520,205 @@ KITEM: ? = potion of healing / potion of restore abilities KITEM: allows you to place multiple items on the same square: KITEM: ? = bread ration, potion of water, potion of porridge -E. Hints for level makers + +E. Conditionalising levels +----------------------------- + +Crawl translated 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. + +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. + + +F. 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 +}} + + +G. Hints for level makers ---------------------------- * Technical stuff: @@ -656,3 +861,163 @@ E. Hints for level makers in the dat directory) can make level-editing far more pleasant by colouring different features in maps and syntax-highlighting .des-file syntax. vim is available for nearly all operating systems, including Windows. + + +H. Lua reference +------------------- + +How maps are processed +---------------------- + +Crawl uses Lua heavily when dealing with .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 prelude and + validation 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> + }} + +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. + + +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. + + +Lua API - global game state +--------------------------- + +The "crawl" module provides functions that describe the game state or +provide utility methods. + +mpr, mesclr, random2, 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 + + +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 diff --git a/crawl-ref/source/clua.cc b/crawl-ref/source/clua.cc index 33047c504e..5448ca6d68 100644 --- a/crawl-ref/source/clua.cc +++ b/crawl-ref/source/clua.cc @@ -717,6 +717,9 @@ LUARET1(you_flying, boolean, player_is_levitating() && wearing_amulet(AMU_CONTROLLED_FLIGHT)) LUARET1(you_transform, string, transform_name()) LUARET1(you_where, string, level_id::current().describe().c_str()) +LUARET1(you_branch, string, level_id::current().describe(false, false).c_str()) +LUARET1(you_subdepth, number, level_id::current().depth) +LUARET1(you_absdepth, number, you.your_level) LUAWRAP(you_stop_activity, interrupt_activity(AI_FORCE_INTERRUPT)) void lua_push_floor_items(lua_State *ls); @@ -770,8 +773,8 @@ static const struct luaL_reg you_lib[] = { "strength" , you_strength }, { "intelligence", you_intelligence }, { "dexterity" , you_dexterity }, - { "exp" , you_exp }, - { "exp_points" , you_exp_points }, + { "xl" , you_exp }, + { "exp" , you_exp_points }, { "res_poison" , you_res_poison }, { "res_fire" , you_res_fire }, @@ -790,7 +793,10 @@ static const struct luaL_reg you_lib[] = { "floor_items", you_floor_items }, - { "where", you_where }, + { "where", you_where }, + { "branch", you_branch }, + { "subdepth", you_subdepth }, + { "absdepth", you_absdepth }, { NULL, NULL }, }; @@ -1543,14 +1549,19 @@ static int crawl_mpr(lua_State *ls) return (0); int ch = MSGCH_PLAIN; - const char *channel = lua_tostring(ls, 2); - if (channel) - ch = str_to_channel(channel); - if (ch == -1) + if (lua_isnumber(ls, 2)) + ch = luaL_checkint(ls, 2); + else + { + const char *channel = lua_tostring(ls, 2); + if (channel) + ch = str_to_channel(channel); + } + + if (ch < 0 || ch >= NUM_MESSAGE_CHANNELS) ch = MSGCH_PLAIN; mpr(message, ch); - return (0); } diff --git a/crawl-ref/source/dungeon.cc b/crawl-ref/source/dungeon.cc index df8dae0ae2..87ee0b0ade 100644 --- a/crawl-ref/source/dungeon.cc +++ b/crawl-ref/source/dungeon.cc @@ -244,7 +244,7 @@ static void place_altars() *********************************************************************/ void builder(int level_number, int level_type) { - // 15 tries to build the level, after which we bail with a capital B. + // N tries to build the level, after which we bail with a capital B. int tries = 10; while (tries-- > 0) { diff --git a/crawl-ref/source/mapdef.cc b/crawl-ref/source/mapdef.cc index 49450734fc..1fb07839b4 100644 --- a/crawl-ref/source/mapdef.cc +++ b/crawl-ref/source/mapdef.cc @@ -1026,35 +1026,44 @@ std::string map_def::run_lua(bool run_main) return (dlua.error); } -bool map_def::test_lua_boolchunk(dlua_chunk &chunk) +bool map_def::test_lua_boolchunk(dlua_chunk &chunk, bool defval, bool croak) { - bool result = true; + bool result = defval; dlua_set_map mset(this); int err = chunk.load(dlua); if (err == -1000) - return (true); + return (result); else if (err) { - mprf(MSGCH_WARN, "Lua error: %s", validate.orig_error().c_str()); - return (true); + if (croak) + end(1, false, "Lua error: %s", validate.orig_error().c_str()); + else + mprf(MSGCH_WARN, "Lua error: %s", validate.orig_error().c_str()); + return (result); } if (dlua.callfn("dgn_run_map", 1, 1)) dlua.fnreturns(">b", &result); else - mprf(MSGCH_WARN, "Lua error: %s", - rewrite_chunk_errors(dlua.error).c_str()); + { + if (croak) + end(1, false, "Lua error: %s", + rewrite_chunk_errors(dlua.error).c_str()); + else + mprf(MSGCH_WARN, "Lua error: %s", + rewrite_chunk_errors(dlua.error).c_str()); + } return (result); } -bool map_def::test_lua_validate() +bool map_def::test_lua_validate(bool croak) { - return test_lua_boolchunk(validate); + return validate.empty() || test_lua_boolchunk(validate, false, croak); } bool map_def::test_lua_veto() { - return test_lua_boolchunk(veto); + return !veto.empty() && test_lua_boolchunk(veto, true); } std::string map_def::rewrite_chunk_errors(const std::string &s) const @@ -1076,6 +1085,8 @@ std::string map_def::validate_map_def() if (!err.empty()) return (err); + test_lua_validate(true); + if (orient == MAP_FLOAT || is_minivault()) { if (map.width() > GXM - MAPGEN_BORDER * 2 diff --git a/crawl-ref/source/mapdef.h b/crawl-ref/source/mapdef.h index 623b97dd55..cfd1488510 100644 --- a/crawl-ref/source/mapdef.h +++ b/crawl-ref/source/mapdef.h @@ -453,7 +453,7 @@ public: std::string run_lua(bool skip_main); // Returns true if the validation passed. - bool test_lua_validate(); + bool test_lua_validate(bool croak = false); // Returns true if *not* vetoed, i.e., the map is good to go. bool test_lua_veto(); @@ -525,7 +525,7 @@ public: private: void write_depth_ranges(FILE *) const; void read_depth_ranges(FILE *); - bool test_lua_boolchunk(dlua_chunk &); + bool test_lua_boolchunk(dlua_chunk &, bool def = false, bool croak = false); std::string rewrite_chunk_errors(const std::string &s) const; std::string add_key_field( diff --git a/crawl-ref/source/maps.cc b/crawl-ref/source/maps.cc index 8b89d52115..dd64467129 100644 --- a/crawl-ref/source/maps.cc +++ b/crawl-ref/source/maps.cc @@ -112,10 +112,15 @@ static int write_vault(map_def &mdef, map_type map, static bool resolve_map(map_def &map, const map_def &original) { map.reinit(); - map.run_lua(true); + std::string err = map.run_lua(true); + if (!err.empty()) + { + mprf(MSGCH_WARN, "Lua error: %s", err.c_str()); + return (false); + } map.resolve(); - if (!map.test_lua_validate()) + if (!map.test_lua_validate(false)) return (false); // Mirroring is possible for any map that does not explicitly forbid it. diff --git a/crawl-ref/source/util/levcomp.lpp b/crawl-ref/source/util/levcomp.lpp index 96331e45d1..cf803cdc00 100644 --- a/crawl-ref/source/util/levcomp.lpp +++ b/crawl-ref/source/util/levcomp.lpp @@ -156,13 +156,13 @@ NSPACE [^\ \t\r\n] ^\s*MAP { BEGIN(MAPDEF); } -^: { BEGIN(LUA_ONELINER); return MAIN; } +^[ \t]*: { BEGIN(LUA_ONELINER); return MAIN; } -^prelude[ \t]*\{\{ { BEGIN(LUA); return PRELUDE; } -^lua[ \t]*\{\{ { BEGIN(LUA); return MAIN; } +^[ \t]*prelude[ \t]*\{\{ { BEGIN(LUA); return PRELUDE; } +^[ \t]*lua[ \t]*\{\{ { BEGIN(LUA); return MAIN; } ^[ \t]*\{\{ { BEGIN(LUA); return MAIN; } -^validate[ \t]*\{\{ { BEGIN(LUA); return VALIDATE; } -^veto[ \t]*\{\{ { BEGIN(LUA); return VETO; } +^[ \t]*validate[ \t]*\{\{ { BEGIN(LUA); return VALIDATE; } +^[ \t]*veto[ \t]*\{\{ { BEGIN(LUA); return VETO; } NAME: { BEGIN(ARGUMENT); return NAME; } |