summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authordshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2007-05-05 07:45:25 +0000
committerdshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2007-05-05 07:45:25 +0000
commita90da56f3e29ff3399a01c28f9e7a66a05b74990 (patch)
tree344e0a924d432aed7bb2222a6806b2d6e3e86b56
parent9406beea182124aeb8b9d71dc829a3bcbc31cde7 (diff)
downloadcrawl-ref-a90da56f3e29ff3399a01c28f9e7a66a05b74990.tar.gz
crawl-ref-a90da56f3e29ff3399a01c28f9e7a66a05b74990.zip
Merge trunk (1403-1409] back to 0.2:
- Level-design howto. - Horn of Geryon now jelly-safe. - Mac users can double-click to run Crawl. - Prices of rings of slaying calculated correctly. - Fixed travel cache losing stair distances. git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/branches/stone_soup-0.2@1410 c06c8d41-db1a-0410-9941-cceddc491573
-rw-r--r--crawl-ref/docs/level-design.txt564
-rw-r--r--crawl-ref/source/AppHdr.h1
-rw-r--r--crawl-ref/source/chardump.cc2
-rw-r--r--crawl-ref/source/externs.h12
-rw-r--r--crawl-ref/source/files.cc25
-rw-r--r--crawl-ref/source/files.h5
-rw-r--r--crawl-ref/source/food.cc4
-rw-r--r--crawl-ref/source/initfile.cc56
-rw-r--r--crawl-ref/source/macro.cc2
-rw-r--r--crawl-ref/source/makefile.unix6
-rw-r--r--crawl-ref/source/monstuff.cc49
-rw-r--r--crawl-ref/source/newgame.cc4
-rw-r--r--crawl-ref/source/shopping.cc2
-rw-r--r--crawl-ref/source/spl-book.cc12
-rw-r--r--crawl-ref/source/travel.cc25
15 files changed, 697 insertions, 72 deletions
diff --git a/crawl-ref/docs/level-design.txt b/crawl-ref/docs/level-design.txt
new file mode 100644
index 0000000000..923f7d313c
--- /dev/null
+++ b/crawl-ref/docs/level-design.txt
@@ -0,0 +1,564 @@
+How to make levels for Dungeon Crawl Stone Soup
+===============================================
+
+Contents: A. Introduction
+ B. Sample map
+ C. Map symbols
+ D. Header information
+ E. Hints for level makers
+
+
+A. Introduction
+-----------------
+
+All fixed level information resides in various .des files to be found in
+the dat directory. If you are interested in adding some vaults, say, start
+with the existing ones and modify them. Currently, the following .des files
+are in use:
+
+ splev.des: * branch endings (like Elf:7, Vaults:8)
+ * premade level maps (like the Ecumenical Temples and Geryon)
+ * Pan lairs of named demons
+ * branch entries (these can ornament the stairs for a branch)
+
+ vaults.des: * random vaults (Crawl sometimes chooses a special level
+ when making up a new level; these are often challenging
+ and sometimes contain loot)
+ * entry vaults (each game - except turorials - uses one of
+ these premade maps for the vicinity of the entrance)
+ * minivaults
+ * Pan minivaults
+
+ tricky.des: * a few entry vaults which are harder in some regard
+ (use of special items, or being puzzles)
+
+ asciiart.des: * a few entry vaults which contain proper ASCII art.
+ These are a matter of taste - some find this annoying.
+
+
+Kinds of Vaults
+---------------
+The different kinds of vaults used by Crawl are described briefly below. In
+most cases, when the dungeon builder places a vault on a level, the rest of the
+level (assuming the vault is not a full-map vault) is generated as
+rooms+corridors. The only exceptions to this are branch entry vaults and
+minivaults, both of which are placed after the rest of the level is generated,
+and therefore do not influence level generation.
+
+* Entry vault:
+
+A map designed for D:1, which (usually) contains the primary upstair { and is
+always tagged "entry". A player starting a new game will usually land in an
+entry vault.
+
+
+* Branch entry vault, or branch portal vault:
+
+A map containing the entry to a branch - either a branch stair (such as the
+stair to the Orcish Mines), or a branch portal (a portal to Hell, say). Always
+tagged "<branchname>_entry".
+
+
+* Special level:
+
+A map for a location of significance in the game, such as the Ecumenical
+Temple, or the end of branches such as level 5 of the Snake Pit (Snake:5).
+Special level maps usually have a PLACE: attribute.
+
+
+* Random vaults:
+
+Random vaults may be randomly generated at any level in the dungeon. Random
+vault maps are selected by the dungeon builder based on their DEPTH:
+attributes.
+
+
+* Random minivaults:
+
+Random minivaults are small maps that are placed onto a level that the dungeon
+builder has already constructed fully otherwise (the level may include other
+vaults).
+
+
+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.
+
+# name below:
+NAME: a_useless_temple_entry_02
+# header section below:
+ORIENT: float
+CHANCE: 5
+TAGS: temple_entry
+FLAGS: no_rotate
+SHUFFLE: de
+SUBST: 1=12.
+MONS: butterfly, plant
+ITEM: stone, w:10 any_good book / w:90 nothing
+# actual map below:
+MAP
+xx1@2xx
+x1wWw2x
+ewwOwwd
+x2www1x
+xx1.1xx
+ENDMAP
+
+"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).
+"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: dw" may replace all 'd' with 'w' in the map.
+"SUBST: 1=12." may replace each '1' with either '1' or '2' or '.'.
+"MONS: butterfly, plant" turns all '1' into butterflies, and '2' into plants.
+"ITEM: stone" turns all 'd' into stones
+"ITEM: w:10 any book / w:100 nothing" turns all 'e' into a book
+ (with 10% chance) or creates no object (with 90% chance).
+
+The symbols on the map:
+ x - rock wall
+ w - water (could be deep or shallow)
+ W - shallow water
+ . - plain floor
+ @ - entry point (this square will be connected to the rest of the map)
+ O - stairs to the Temple
+ 1 - first monster from the list (here butterfly) - note the SUBST: 1=12.
+ 2 - second monster from the list (plant)
+ d - first item from the list (here stones)
+ e - second item from the list (here occassionally a book)
+
+
+D. Map symbols
+----------------
+
+Terrain
+-------
+ x - rock wall (DNGN_ROCK_WALL)
+ X - permanent rock wall - always undiggable (DNGN_PERMAROCK_WALL)
+ c - stone wall - only affected by Shatter (DNGN_STONE_WALL)
+ v - metal wall - grounds electricity (DNGN_METAL_WALL)
+ b - crystal wall - reflects cold and fire (DNGN_GREEN_CRYSTAL_WALL)
+ a - wax wall - can melt (DNGN_WAX_WALL)
+
+ . - floor (DNGN_FLOOR)
+ + - closed door (DNGN_CLOSED_DOOR)
+ = - secret door (DNGN_SECRET_DOOR)
+
+ W - shallow water
+ w - deep water - can be turned into shallow water; prevent this with the
+ no_pool_fixup TAG. Also, this may receive water creatures! For entry
+ vaults, avoid this with the no_monster_gen TAG.
+ l - lava - again, use the no_monster_gen TAG for entry vaults!
+
+Features
+--------
+ @ - entry point - must be on outside and, except in ORIENT:float layouts,
+ must always be on a particular side or sides - see templates. If you use
+ ORIENT: float, and do not use any @, the dungeon builder will connect at
+ least one floorspace on the edge of your map to the rest of the level; if
+ there is no floorspace on the edge of your map, it will be isolated.
+ }{ - stairs 82/86 - You must be able to reach these from each other. The
+ { upstair is also the stair on which the player will enter the
+ dungeon for entry vaults.
+ )( - stairs 83/87
+ ][ - stairs 84/88
+
+ >< - extra rock stairs - you can leave level by these but will rarely be
+ placed on them from another level
+
+ I - orcish idol (does nothing)
+
+ ^ - random trap.
+ ~ - random trap suitable for the branch and depth the map is being used.
+
+ A - Vestibule gateway (opened by Horn).
+ B - Altar. These are assigned specific types (eg of Zin etc) in dungeon.cc,
+ in order.
+ C - Random Altar.
+
+ F - Usually a Granite Statue, but may be Orange or Silver or Ice (1 in 100)
+ G - Granite statue (does nothing) - you can see through but not walk through
+ H - orange crystal statue (attacks mind)
+ S - Silver statue (summons demons). Avoid using (rare).
+
+ T - Water fountain
+ U - Magic fountain
+ V - Permanently dry fountain
+
+# Note: Due to the level maker having seen incremental improvements over the
+# years, there are some inconsistencies. For examples, dangerous statues
+# (orange, silver ice) are now genuine monsters. In particular, the 'G' and 'S'
+# glyphs could be dispensed with (but many older vaults use them, of course) -
+# especially as there's no glyph for ice statues.
+#
+# Also, the most of the other feature glyphs can be replaced with KFEAT:
+# lines. The same goes for some item glyphs ('O', 'P', 'R', 'Z') which could
+# be replaced by KITEM: lines.
+
+Items
+-----
+ $ - gold
+ % - normal item
+ * - higher level item (good)
+ | - acquirement-level item (almost guaranteed excellent)
+ O - place an appropriate rune here. For portal vaults, place the portal here.
+ P - maybe place a rune here (50%)
+ R - honeycomb (2/3) or royal jelly (1/3)
+ Z - the Orb of Zot
+ d-k - item array item. See section below on ITEM: arrays for more info.
+
+Monsters
+--------
+ 0 - normal monster
+ 9 - +5 depth monster
+ 8 - (+2) * 2 depth monster (aargh!). Can get golden dragons and titans
+ this way.
+ 1-7 - monster array monster. See section below on MONS: arrays for more
+ information
+
+
+D. Header information
+-----------------------
+
+NAME: a_string
+ Each map must have a unique name. Underscores and digits are ok.
+
+ORIENT: (float |encompass | north | northwest | ... | southeast)
+
+ Some kind of ORIENT: line is mandatory, unless you want the vault to
+ be a minivault, which is usually not what you want. Valid values are:
+ * "float": The dungeon builder puts your vault wherever it wants to.
+ * "some_direction": The vault lies along that side of the map:
+ xxxxxxxxxx xxxxxxxxxxxxx
+ xORIENT:Nx xORIENT:NW|..
+ x.VAULT..x x.VAULT...|..
+ x--------x x---------|..
+ xrest....x xrest........
+ x...of...x x.....of.....
+ x...levelx x.......level
+ ...which brings us to padding. With any some_direction orientation,
+ you need 6 layers of x-padding along any level-edge that the vault
+ borders. For instance, if your map is ORIENT: north, you must have a
+ 6 deep border of rock wall (or any other kind of wall) along the
+ northern, eastern, and western edges of the map.
+ * "encompass": the vault completely occupies the entire level.
+ Padding is needed on all 4 sides.
+
+DEPTH: For random vaults and minivaults, this gives the range where the vault
+ may be placed in the main dungeon. E.g.
+ DEPTH: 7-20
+
+CHANCE: (number with 10 being default)
+ For entry vaults and any other vaults randomly picked from among
+ a set, this type of line affects the likelihood of the given vault
+ being picked in a given game. The default CHANCE: is 10. The
+ likelihood of a vault getting picked is:
+ [vault's CHANCE: / sum of all CHANCE:s of vaults of that type]
+
+PLACE: Used to specify certain special levels. Existing special levels are:
+ Temple, Hell, Dis:7, Geh:7, Coc:7, Tar:7, Hive:4, Vault:8, Snake:5,
+ Elf:7, Slime:6, Blade, Zot:5, Tomb:1, Tomb:2, Tomb:3, Swamp:5.
+
+ PLACE can also be used to specify arbitrary places, like D:3, which
+ will force the map (or one of the maps with PLACE: D:3) to be picked
+ when D:3 is generated.
+
+ PLACE cannot be used to specify places in the Abyss, Pandemonium,
+ or Labyrinths.
+
+ PLACE can be used with random vaults and minivaults for testing them.
+
+TAGS: generate_awake, no_item_gen, no_monster_gen, no_pool_fixup, orc_entry
+ Tags go an a TAGS: line and are space-separated. Valid tags are:
+ * "entry": this tag MUST be there for a vault to be pickable as
+ an entry vault.
+ * "generate_awake": Monsters placed (using MONS, KMONS) in this
+ vault will be generated awake.
+ * "no_item_gen": Prevents random item generation in the vault.
+ Items explicitly placed by the vault are not affected.
+ * "no_monster_gen": Prevents random monster generation at the time
+ of the vault's creation. Highly advised for entry vaults with
+ a player-hostile geography, MUST-HAVE for those with water/lava.
+ * "no_pool_fixup": prevents water squares next to land from being
+ randomly converted from deep water (the default) to shallow.
+ * "branch_entry" eg. "orc_entry", "lair_entry" etc. Currently used:
+ temple_entry, orc_entry, vault_entry, lair_entry, hive_entry.
+ If chosen, these maps will contain the stairs for that branch.
+ Use "O" to place the stairs. Branch entries should go to splev.des.
+ As long as a branch has very few entries, a dummy one is a must.
+ * "mnoleg" or the name of some other pandemonium lord. This makes
+ the map eligible for said pan lord's lair (and it can't appear
+ anywhere else).
+
+FLAGS: no_rotate, no_hmirror, no_vmirror
+ Flags go on a FLAGS: line and are space-separated. Valid flags are:
+ * "no_rotate": Normally, the dungeon builder can, at its whim,
+ rotate your vault. This flag tells it, "hey, don't do that to my
+ vault!"
+ * "no_hmirror": Like no_rotate, but for horizontal mirroring.
+ * "no_vmirror": Like no_rotate, but for vertical mirroring.
+
+ITEM: (list of items, separated by comma)
+ These are used to help place specified items at specific places
+ within a vault. They create an array with up to 8 positions. What's
+ in the first position in the array will be used when the dungeon
+ builder sees a "d" in the vault definition, the second will be used
+ for "e"s, etc. Positions are comma-separated; several ITEM: lines
+ are possible as well. The following defines letters 'd' - 'g':
+ ITEM: stone, ring mail, meat ration, ring of hunger
+
+ Positions can contain multiple possibilities, one of which the
+ builder will choose randomly. Separate such multiple possibilities
+ using a slash. Note that "nothing" (without the quotes) is a valid
+ possibility. The random choice is done for each individual occurence
+ of the letter. You can also give possibilities a "weight," which
+ affects their chance of being picked. The default weight is 10. You
+ can abbreviate "weight:30" by "w:30". The chance to pick a
+ possibility is
+ [possibility's weight: / sum of all weight:s in that array position]
+ For example, the following line makes letter 'd' into a bread ration
+ with 50% chance, or apple or orange with 25% chance each:
+ ITEM: bread ration / w:5 apple / w:5 orange
+
+ Modifiers:
+ * "good_item" makes the builder try to make the item a good one.
+ * "any" by itself gives random choice; you can combine "any" with
+ "good_item."
+ * "any book", "any misc" etc. gives a random item of that class.
+
+ Limitations: You can't affect stack quantity for stackable items,
+ nor can you affect curse status nor item race, nor can you give
+ specific egos, nor can give fixedarts. You also can't lay down
+ corpses, skeletons, or chunks.
+
+MONS: (list of monsters)
+ These are used to help place specific monsters at specific places
+ in a vault. They create an array with up to 7 positions. What's in
+ the first position in the array will be used when the dungeon
+ builder sees a "1" in the vault definition, the second for "2,"
+ etc. Note that if, for example, you place a 3 on the map, but your
+ MONS: line has no third position, the 3 will be filled with
+ RANDOM_MONSTER.
+ You can use weights as for ITEM: lines.
+
+ Individual monsters may be prefixed with the "generate_awake"
+ (without the quotes). Use this sparingly.
+ Note that 8, 9, 0 also place monsters (see the table).
+
+
+SHUFFLE: def, 12/3?
+ This allows you to randomly permute glyphs on the map. There are
+ two ways:
+
+ SHUFFLE: 123w (i.e. list of glyphs, NOT slash-separated)
+ could, for example, swap all occurences of "1" with "2", as well as
+ swapping all "3" with "w" (or any other of the 24 possibilities).
+
+ SHUFFLE: 12/3w (i.e. list of slash-separated blocks of same size)
+ will either do nothing or swap all "1" with "3" and then also swap
+ "2" with "w" everywhere.
+
+ Several SHUFFLE: lines can be used, and mixed with SUBST:, and the
+ shuffles and substitutions will be applied in order. You can also
+ put multiple SHUFFLEs on one line, comma-separated. Shuffles cannot
+ use , or /. All spaces are stripped before shuffling.
+
+SUBST: ?=xc, !:bv, 1=2 1:100
+ The SUBST: directive allows you to specify a placeholder symbol
+ that is replaced with a random glyph from a set. For instance:
+
+ SUBST: ? = TUV
+ replaces occurrences of ? with one of TUV. Since whitespaces are
+ irrelevant, this is the same as
+ SUBST: ? = T U V
+
+ SUBST: ? = T:20 U V
+ makes T twice as likely to be used as U or V (the default weight
+ is 10). Note that there has to be at least one space before and
+ after T:20 and that whitespace in T:20 is not permitted.
+
+ SUBST: ? : TUV
+ replaces occurrences of ? with one of TUV, and guarantees that all
+ occurrences of ? will get the same replacement symbol.
+
+ The placeholder and replacement symbols can be any non-space,
+ printable character, including : and =, apart from commas. For
+ example, the following is valid:
+ SUBST: = = +=:123def"
+
+ SUBST: lines can safely replace symbols with themselves, as in:
+ SUBST: w = wW
+
+ Multiple SUBST: lines can be used, and mixed with SHUFFLE:, and
+ will be applied in order. Multiple substitutions can be performed
+ on one line, using commas.
+
+KFEAT: Z = C / needle trap / antique armour shop / altar of Zin
+ The KFEAT: directive allows you to specify a placeholder symbol
+ that is replaced with another symbol, named feature, trap, or
+ shop. For example, the line above will replace occurrences of Z
+ with C (random altar), a needle trap, an antique armour shop, or
+ an altar of Zin. Different instances of Z may receive different
+ replacements. To force a single replacement for all Z, use:
+ KFEAT: Z : C / needle trap / antique armour shop
+ You'll notice that 'Z' is the symbol of the Orb of Zot. Kxxx
+ directives allow you to assign arbitrary definitions to any symbol.
+
+ If you want no feature as an option in a KFEAT line, use 'floor'.
+ If you do not want to specify the type of shop, use 'any shop' or
+ 'random shop'.
+
+ The placeholder used by KFEAT can be shared by KITEM and KMONS;
+ see below. If the placeholder is shared, all defined Kxxxx
+ operations for the placeholder are performed. Also, all Kxxx
+ lines accept weights as for MONS or ITEM.
+
+KMONS: ? = orc priest / w:3 deep elf priest
+ KMONS: allows you to specify a placeholder symbol that indicates
+ the position of a monster (or monsters).
+ Using KMONS: allows you to exceed the 7 slot limit for monsters.
+ It is also useful if you want to place a monster on a non-floor
+ square (used in association with a KFEAT:). Fr example,
+ KFEAT: Z = W
+ KMONS: Z = rat
+ places a rat on a shallow water square for all occurrences of Z.
+
+KITEM: ? = potion of healing / potion of restore abilities
+ KITEM: places the specified item at all occurrences of the
+ placeholder. It can be combined with KFEAT: and KMONS: lines for
+ the same placeholder.
+
+ For items like gold or fountains, you have to use the description
+ of items instead of their shortcuts. For example,
+ KITEM: none / gold
+ works but the following does not:
+ KITEM: none / $
+
+E. Hints for level makers
+----------------------------
+
+* Technical stuff:
+ If your map is not a minivault or a floating vault, make sure the
+ side(s) forming the border have a rock wall padding at least 6 deep. For
+ instance, if your map is ORIENT: north, you must have a 6 deep border of
+ rock wall (or any other kind of wall) along the northern, eastern, and
+ western edges of the map. If you're doing a fullscreen map (encompass),
+ you must pad all around the map with 6 layers of wall.
+
+ You do not have to place all of the stairs unless the level is full
+ screen, in which case you must place all except the extra stairs (> and
+ <). The <> stairs can be put anywhere and in any quantities but do not
+ have to be there. Any of the other stairs which are not present in the
+ vault will be randomly placed outside it. Also generally try to avoid
+ rooms with no exit (use at least > or < to make it possible for players
+ to get away).
+
+ The entry point '@' must be present (except full-screen vaults where it
+ must not). For ORIENT: float maps, all @ will be connected to floors in
+ the rest of the map. Make sure that no part of your entry level can be
+ cut off! If no @ is present, the level builder will put one on a random
+ floor space '.' at the circumference. (Sometimes this may be used for
+ good effect. When you give no @'s with this feature in mind, please make
+ a comment stating this - else somebody may just add @'s later on :)
+
+ Note that non-rectangular maps will be padded with rock walls for the
+ smallest rectangle containing them. Unfortunately.
+
+ Entry levels should be rather small. Their intention is to provide some
+ atmosphere for the starting room, not to get a grip on the whole of D:1.
+ Minivaults should be rather small, as well, in order to increase the
+ chances they may actually be chosen during level generation.
+
+* Randomise!
+ The level making syntax is now very supportive for making a single map
+ appear in many versions. Use the SHUFFLE: and SUBST: directives and look
+ at the existing entry vaults. Besides reducing tedium, this avoids giving
+ veterans a spoiled edge. For example, if a secret chamber with loot is
+ always at the same place, it's a no-brainer for those who know. The same
+ goes for traps. This is much less so if there are several places for the
+ chamber (or trap) and there's even a chance it doesn't exist.
+
+ You can also use CHANCE to create modified versions of the same map. In
+ order to do this, make several maps and endow each with a chance such
+ that the sum of chances add up to 10.
+
+ Randomisation does not just apply to layout: you could also have
+ different monster population sets (for example make a branch end skewed
+ for either melee or ranged opponents), or perhaps couple difficulty to
+ loot.
+
+* Not too much loot.
+ For example, entry vaults should in general have very little loot - in
+ particular no good_xxx or '*' items lest they might give incentive for
+ start-scumming. For random vaults, there needn't be loot at all and, in
+ any case, there shouldn't be too much of it. Compare with the branch ends
+ rich in treasure (Tomb:3, Cocytus etc.) to get a feeling for this.
+
+* Have a theme.
+ It is often worthwhile (to me at least) to have a theme in mind before
+ making the actual level. For entry vaults, something simple like
+ 'fortress' or 'forest' may be enough. For later (or larger) maps, try
+ to think of distinguishing features your map may have. Being cool can
+ be good enough, but possessing some gameplay value (for example by being
+ easier for particular skills/capabilities like ranged attacks or
+ necromancy or Traps & Doors) is even better.
+
+* Testing your maps.
+ This is easy for entry vaults. Temporarily introducing a CHANCE: 5000
+ will make your entry appear almost always. For other vaults, you can
+ for the moment declare them as entry vaults with a huge CHANCE: as
+ above (and preferably in wizard mode). For more intricate things like
+ new branch ends, you have to resort to wizard mode and use the &~ command
+ to go directly to the place where the map is used, say Snake:5. You may want
+ to use a high CHANCE: again, if the map has alternatives (like Snake:5, or
+ Coc:7).
+
+ If the .des file syntax is incorrect, Crawl will tell you on which line of
+ which des file it found the syntax error, making for easier debugging.
+
+* Be fair!
+ Crawl is hard but try to balance your monsters. While it is true that Orc:1
+ can show an orcish knight, this is very rare. Hence it's probably a bad idea
+ to use orcish knights for an entry to the Orcish Mines.
+
+ Phrased more generally, do not use OOD (out-of-depth) monsters unless you
+ really know what you want.
+
+ Be especially fair when creating entry vaults. If your entry is too
+ hard, it might get degraded to tricky.des (or just removed). Keep in
+ mind that your vault will be played very very often, so even small
+ chances of something stupid happening (like creation of a really nasty
+ monster) will kick in often enough.
+
+* Minivaults vs random vaults.
+ Minivaults are handled very differently from regular vaults and special
+ levels. They're placed *after* normal map generation, whereas normal
+ vaults are placed before generating the rest of the level. There's no
+ way to guarantee generation of a minivault on a particular level, although
+ using a PLACE: attribute makes the dungeon builder try very hard to place
+ the minivault on the specified level. Regular vaults can always be forced to
+ appear using a PLACE: attribute.
+
+ Technically, you make a minivault like a normal floating vault but
+ without an ORIENT: line. Note that minivaults used to be exclusively of
+ size 12x12 but this restriction is gone. Still, the smaller the better.
+
+* levdes.vim.
+ Your choice of editor can greatly affect your level building. While vi's
+ reputation may be close to Crawl's when it comes to fiendishness, there
+ is a style file (levdes.vim in the dat directory) which helps tremendously
+ (it colours the various glyphs on the maps, and it highlights the syntax).
+ Even Windows users can profit from this by using gvim, a freeware vim
+ clone with a graphical user interface which knows shortcuts like Ctrl-X
+ etc.
diff --git a/crawl-ref/source/AppHdr.h b/crawl-ref/source/AppHdr.h
index 8bad5c10e4..29215ba520 100644
--- a/crawl-ref/source/AppHdr.h
+++ b/crawl-ref/source/AppHdr.h
@@ -174,6 +174,7 @@
// NT and better are happy with /; I'm not sure how 9x reacts.
#define FILE_SEPARATOR '/'
+ #define ALT_FILE_SEPARATOR '\\'
// Uncomment to play sounds. winmm must be linked in if this is uncommented.
// #define WINMM_PLAY_SOUNDS
diff --git a/crawl-ref/source/chardump.cc b/crawl-ref/source/chardump.cc
index 3319e307ad..1ed9daeeaa 100644
--- a/crawl-ref/source/chardump.cc
+++ b/crawl-ref/source/chardump.cc
@@ -962,7 +962,7 @@ static std::string morgue_directory()
{
std::string dir =
!Options.morgue_dir.empty()? Options.morgue_dir :
- SysEnv.crawl_dir ? SysEnv.crawl_dir : "";
+ !SysEnv.crawl_dir.empty() ? SysEnv.crawl_dir : "";
if (!dir.empty() && dir[dir.length() - 1] != FILE_SEPARATOR)
dir += FILE_SEPARATOR;
diff --git a/crawl-ref/source/externs.h b/crawl-ref/source/externs.h
index 6bdf8f1562..004cc278c4 100644
--- a/crawl-ref/source/externs.h
+++ b/crawl-ref/source/externs.h
@@ -1132,11 +1132,13 @@ extern std::vector<ghost_demon> ghosts;
struct system_environment
{
- char *crawl_name;
- char *crawl_pizza;
- char *crawl_rc;
- char *crawl_dir;
- char *home; // only used by MULTIUSER systems
+ std::string crawl_name;
+ std::string crawl_pizza;
+ std::string crawl_rc;
+ std::string crawl_dir;
+ std::string crawl_base; // Directory from argv[0], may be used to
+ // locate datafiles.
+ std::string home; // only used by MULTIUSER systems
bool board_with_nail; // Easter Egg silliness
#ifdef DGL_SIMPLE_MESSAGING
diff --git a/crawl-ref/source/files.cc b/crawl-ref/source/files.cc
index 07b4676347..0f1b12150c 100644
--- a/crawl-ref/source/files.cc
+++ b/crawl-ref/source/files.cc
@@ -218,6 +218,19 @@ std::vector<std::string> get_dir_files(const std::string &dirname)
return (files);
}
+std::string get_parent_directory(const std::string &filename)
+{
+ std::string::size_type pos = filename.rfind(FILE_SEPARATOR);
+ if (pos != std::string::npos)
+ return filename.substr(0, pos + 1);
+#ifdef ALT_FILE_SEPARATOR
+ pos = filename.rfind(ALT_FILE_SEPARATOR);
+ if (pos != std::string::npos)
+ return filename.substr(0, pos + 1);
+#endif
+ return ("");
+}
+
static bool file_exists(const std::string &name)
{
FILE *f = fopen(name.c_str(), "r");
@@ -277,9 +290,14 @@ static bool create_dirs(const std::string &dir)
return (true);
}
-std::string datafile_path(const std::string &basename, bool croak_on_fail)
+std::string datafile_path(const std::string &basename,
+ bool croak_on_fail,
+ bool test_base_path)
{
- std::string cdir = SysEnv.crawl_dir? SysEnv.crawl_dir : "";
+ if (test_base_path && file_exists(basename))
+ return (basename);
+
+ std::string cdir = !SysEnv.crawl_dir.empty()? SysEnv.crawl_dir : "";
const std::string rawbases[] = {
#ifdef DATA_DIR_PATH
@@ -291,7 +309,6 @@ std::string datafile_path(const std::string &basename, bool croak_on_fail)
const std::string prefixes[] = {
std::string("dat") + FILE_SEPARATOR,
- std::string("data") + FILE_SEPARATOR,
std::string("docs") + FILE_SEPARATOR,
std::string("..")+FILE_SEPARATOR+std::string("docs")+FILE_SEPARATOR,
std::string("..") + FILE_SEPARATOR,
@@ -312,6 +329,8 @@ std::string datafile_path(const std::string &basename, bool croak_on_fail)
}
#ifndef DATA_DIR_PATH
+ if (!SysEnv.crawl_base.empty())
+ bases.push_back(SysEnv.crawl_base);
bases.push_back("");
#endif
diff --git a/crawl-ref/source/files.h b/crawl-ref/source/files.h
index ff0af7c108..59ea5a9bde 100644
--- a/crawl-ref/source/files.h
+++ b/crawl-ref/source/files.h
@@ -27,8 +27,9 @@
extern FixedArray<bool, MAX_LEVELS, NUM_BRANCHES> tmp_file_pairs;
std::string datafile_path(const std::string &basename,
- bool croak_on_fail = true);
-
+ bool croak_on_fail = true,
+ bool test_base_path = false);
+std::string get_parent_directory(const std::string &filename);
bool check_dir(const std::string &what, std::string &dir);
bool travel_load_map( char branch, int absdepth );
diff --git a/crawl-ref/source/food.cc b/crawl-ref/source/food.cc
index 185b83185e..81da3e2164 100644
--- a/crawl-ref/source/food.cc
+++ b/crawl-ref/source/food.cc
@@ -1033,8 +1033,8 @@ static void eating(unsigned char item_class, int item_type)
restore_stat(STAT_ALL, false);
break;
case FOOD_PIZZA:
- if (SysEnv.crawl_pizza && !one_chance_in(3))
- snprintf(info, INFO_SIZE, "Mmm... %s", SysEnv.crawl_pizza);
+ if (!SysEnv.crawl_pizza.empty() && !one_chance_in(3))
+ mprf("Mmm... %s.", SysEnv.crawl_pizza.c_str());
else
{
temp_rand = random2(9);
diff --git a/crawl-ref/source/initfile.cc b/crawl-ref/source/initfile.cc
index b948c557e8..4bd3855747 100644
--- a/crawl-ref/source/initfile.cc
+++ b/crawl-ref/source/initfile.cc
@@ -882,11 +882,11 @@ void game_options::add_cset_override(char_set_type set, dungeon_char_type dc,
std::string read_init_file(bool runscript)
{
const char* locations_data[][2] = {
- { SysEnv.crawl_rc, "" },
- { SysEnv.crawl_dir, "init.txt" },
+ { SysEnv.crawl_rc.c_str(), "" },
+ { SysEnv.crawl_dir.c_str(), "init.txt" },
#ifdef MULTIUSER
- { SysEnv.home, "/.crawlrc" },
- { SysEnv.home, "init.txt" },
+ { SysEnv.home.c_str(), "/.crawlrc" },
+ { SysEnv.home.c_str(), "init.txt" },
#endif
{ "", "init.txt" },
#ifdef WIN32CONSOLE
@@ -1527,10 +1527,17 @@ void game_options::read_option_line(const std::string &str, bool runscript)
else if (key == "lua_file" && runscript)
{
#ifdef CLUA_BINDINGS
- clua.execfile(field.c_str());
- if (clua.error.length())
- fprintf(stderr, "Lua error: %s\n",
- clua.error.c_str());
+ const std::string lua_file = datafile_path(field, false, true);
+ if (lua_file.empty())
+ {
+ fprintf(stderr, "Unable to find lua file: %s\n", field.c_str());
+ }
+ else
+ {
+ clua.execfile(lua_file.c_str());
+ if (clua.error.length())
+ fprintf(stderr, "Lua error: %s\n", clua.error.c_str());
+ }
#endif
}
else if (key == "colour" || key == "color")
@@ -1786,14 +1793,7 @@ void game_options::read_option_line(const std::string &str, bool runscript)
{
// We shouldn't bother to allocate this a second time
// if the user puts two crawl_dir lines in the init file.
- if (!SysEnv.crawl_dir)
- SysEnv.crawl_dir = (char *) calloc(kPathLen, sizeof(char));
-
- if (SysEnv.crawl_dir)
- {
- strncpy(SysEnv.crawl_dir, field.c_str(), kPathLen - 1);
- SysEnv.crawl_dir[ kPathLen - 1 ] = 0;
- }
+ SysEnv.crawl_dir = field;
}
else if (key == "race")
{
@@ -2360,17 +2360,22 @@ void game_options::read_option_line(const std::string &str, bool runscript)
}
}
+static std::string check_string(const char *s)
+{
+ return (s? s : "");
+}
+
void get_system_environment(void)
{
// The player's name
- SysEnv.crawl_name = getenv("CRAWL_NAME");
+ SysEnv.crawl_name = check_string( getenv("CRAWL_NAME") );
// The player's pizza
- SysEnv.crawl_pizza = getenv("CRAWL_PIZZA");
+ SysEnv.crawl_pizza = check_string( getenv("CRAWL_PIZZA") );
// The directory which contians init.txt, macro.txt, morgue.txt
// This should end with the appropriate path delimiter.
- SysEnv.crawl_dir = getenv("CRAWL_DIR");
+ SysEnv.crawl_dir = check_string( getenv("CRAWL_DIR") );
#ifdef DGL_SIMPLE_MESSAGING
// Enable DGL_SIMPLE_MESSAGING only if SIMPLEMAIL and MAIL are set.
@@ -2383,17 +2388,24 @@ void get_system_environment(void)
#endif
// The full path to the init file -- this over-rides CRAWL_DIR
- SysEnv.crawl_rc = getenv("CRAWL_RC");
+ SysEnv.crawl_rc = check_string( getenv("CRAWL_RC") );
// rename giant and giant spiked clubs
SysEnv.board_with_nail = (getenv("BOARD_WITH_NAIL") != NULL);
#ifdef MULTIUSER
// The user's home directory (used to look for ~/.crawlrc file)
- SysEnv.home = getenv("HOME");
+ SysEnv.home = check_string( getenv("HOME") );
#endif
} // end get_system_environment()
+static void set_crawl_base_dir(const char *arg)
+{
+ if (!arg)
+ return;
+
+ SysEnv.crawl_base = get_parent_directory(arg);
+}
// parse args, filling in Options and game environment as we go.
// returns true if no unknown or malformed arguments were found.
@@ -2427,6 +2439,8 @@ bool arg_seen[num_cmd_ops];
bool parse_args( int argc, char **argv, bool rc_only )
{
+ set_crawl_base_dir(argv[0]);
+
if (argc < 2) // no args!
return (true);
diff --git a/crawl-ref/source/macro.cc b/crawl-ref/source/macro.cc
index 2420971a40..ee444f24f7 100644
--- a/crawl-ref/source/macro.cc
+++ b/crawl-ref/source/macro.cc
@@ -171,7 +171,7 @@ static std::string get_macro_file()
{
std::string dir =
!Options.macro_dir.empty()? Options.macro_dir :
- SysEnv.crawl_dir? SysEnv.crawl_dir : "";
+ !SysEnv.crawl_dir.empty()? SysEnv.crawl_dir : "";
if (!dir.empty())
{
diff --git a/crawl-ref/source/makefile.unix b/crawl-ref/source/makefile.unix
index e1516fb05e..6ab25c5445 100644
--- a/crawl-ref/source/makefile.unix
+++ b/crawl-ref/source/makefile.unix
@@ -155,8 +155,10 @@ install: $(GAME)
ifeq ($(DATADIR),)
$(error DATADIR not set! Set DATADIR and run make clean install again)
endif
- mkdir -p $(DATADIR)/data
- cp dat/*.des $(DATADIR)/data
+ mkdir -p $(DATADIR)/dat
+ cp dat/*.des $(DATADIR)/dat
+ cp dat/*.txt $(DATADIR)/dat
+ cp -r lua $(DATADIR)/dat
mkdir -p $(DATADIR)/docs
cp ../docs/*.txt $(DATADIR)/docs
chown -R $(INSTALL_UGRP) $(DATADIR)
diff --git a/crawl-ref/source/monstuff.cc b/crawl-ref/source/monstuff.cc
index fd0cf3f637..a14d593a2e 100644
--- a/crawl-ref/source/monstuff.cc
+++ b/crawl-ref/source/monstuff.cc
@@ -3784,6 +3784,32 @@ static bool monster_wants_weapon(const monsters *monster, const item_def &weap)
return (true);
}
+static bool is_item_jelly_edible(const item_def &item)
+{
+ // don't eat artefacts (note that unrandarts are randarts)
+ if (is_fixed_artefact(item) || is_random_artefact(item))
+ return (false);
+
+ // shouldn't eat stone things
+ // - but what about wands and rings?
+ if (item.base_type == OBJ_MISSILES
+ && (item.sub_type == MI_STONE || item.sub_type == MI_LARGE_ROCK))
+ {
+ return (false);
+ }
+
+ // don't eat special game items
+ if (item.base_type == OBJ_ORBS
+ || (item.base_type == OBJ_MISCELLANY
+ && (item.sub_type == MISC_RUNE_OF_ZOT
+ || item.sub_type == MISC_HORN_OF_GERYON)))
+ {
+ return (false);
+ }
+
+ return (true);
+}
+
//---------------------------------------------------------------
//
// handle_pickup
@@ -3816,28 +3842,9 @@ static bool handle_pickup(struct monsters *monster)
{
int quant = mitm[item].quantity;
- // don't eat artefacts (note that unrandarts are randarts)
- if (is_fixed_artefact(mitm[item]) ||
- is_random_artefact(mitm[item]))
- continue;
-
- // shouldn't eat stone things
- // - but what about wands and rings?
- if (mitm[item].base_type == OBJ_MISSILES
- && (mitm[item].sub_type == MI_STONE
- || mitm[item].sub_type == MI_LARGE_ROCK))
- {
- continue;
- }
-
- // don't eat special game items
- if (mitm[item].base_type == OBJ_ORBS
- || (mitm[item].base_type == OBJ_MISCELLANY
- && mitm[item].sub_type == MISC_RUNE_OF_ZOT))
- {
+ if (!is_item_jelly_edible(mitm[item]))
continue;
- }
-
+
if (mitm[igrd[monster->x][monster->y]].base_type != OBJ_GOLD)
{
if (quant > max_eat - eaten)
diff --git a/crawl-ref/source/newgame.cc b/crawl-ref/source/newgame.cc
index 196bf9e142..363c9f5c19 100644
--- a/crawl-ref/source/newgame.cc
+++ b/crawl-ref/source/newgame.cc
@@ -384,9 +384,9 @@ bool new_game(void)
// copy name into you.your_name if set from environment --
// note that you.your_name could already be set from init.txt
// this, clearly, will overwrite such information {dlb}
- if (SysEnv.crawl_name)
+ if (!SysEnv.crawl_name.empty())
{
- strncpy( you.your_name, SysEnv.crawl_name, kNameLen );
+ strncpy( you.your_name, SysEnv.crawl_name.c_str(), kNameLen );
you.your_name[ kNameLen - 1 ] = 0;
}
diff --git a/crawl-ref/source/shopping.cc b/crawl-ref/source/shopping.cc
index 31c52d943d..d988e1e6d0 100644
--- a/crawl-ref/source/shopping.cc
+++ b/crawl-ref/source/shopping.cc
@@ -1407,7 +1407,7 @@ unsigned int item_value( item_def item, id_arr id, bool ident )
valued += 10 * item.plus;
if (item.sub_type == RING_SLAYING && item.plus2 > 0)
- valued += 10 * item.plus;
+ valued += 10 * item.plus2;
}
switch (item.sub_type)
diff --git a/crawl-ref/source/spl-book.cc b/crawl-ref/source/spl-book.cc
index 2f850333c0..c5fb33aebd 100644
--- a/crawl-ref/source/spl-book.cc
+++ b/crawl-ref/source/spl-book.cc
@@ -1231,6 +1231,18 @@ bool learn_spell(void)
return (false);
}
+ if (you.conf)
+ {
+ mpr("You are too confused!");
+ return (false);
+ }
+
+ if (you.berserker)
+ {
+ canned_msg(MSG_TOO_BERSERK);
+ return (false);
+ }
+
if (!which_spellbook( book, spell ))
return (false);
diff --git a/crawl-ref/source/travel.cc b/crawl-ref/source/travel.cc
index 19299a1506..dfdb0ab4fd 100644
--- a/crawl-ref/source/travel.cc
+++ b/crawl-ref/source/travel.cc
@@ -2237,7 +2237,6 @@ static int find_transtravel_stair( const level_id &cur,
for (int i = 0, count = stairs.size(); i < count; ++i)
{
stair_info &si = stairs[i];
-
int deltadist = li.distance_between(this_stair, &si);
if (!this_stair)
{
@@ -2250,7 +2249,8 @@ static int find_transtravel_stair( const level_id &cur,
// deltadist == 0 is legal (if this_stair is NULL), since the player
// may be standing on the stairs. If two stairs are disconnected,
// deltadist has to be negative.
- if (deltadist < 0) continue;
+ if (deltadist < 0)
+ continue;
int dist2stair = distance + deltadist;
if (si.distance == -1 || si.distance > dist2stair)
@@ -2299,7 +2299,8 @@ static int find_transtravel_stair( const level_id &cur,
}
// If we don't know where these stairs go, we can't take them.
- if (!dest.is_valid()) continue;
+ if (!dest.is_valid())
+ continue;
// We need to get the stairs at the new location and set the
// distance on them as well.
@@ -2768,13 +2769,12 @@ void LevelInfo::correct_stair_list(const std::vector<coord_def> &s)
stair_distances.clear();
// First we kill any stairs in 'stairs' that aren't there in 's'.
- for (std::vector<stair_info>::iterator i = stairs.begin();
- i != stairs.end(); ++i)
+ for (int i = ((int) stairs.size()) - 1; i >= 0; --i)
{
bool found = false;
for (int j = s.size() - 1; j >= 0; --j)
{
- if (s[j] == i->position)
+ if (s[j] == stairs[i].position)
{
found = true;
break;
@@ -2782,7 +2782,7 @@ void LevelInfo::correct_stair_list(const std::vector<coord_def> &s)
}
if (!found)
- stairs.erase(i--);
+ stairs.erase(stairs.begin() + i);
}
// For each stair in 's', make sure we have a corresponding stair
@@ -2817,7 +2817,9 @@ void LevelInfo::correct_stair_list(const std::vector<coord_def> &s)
}
}
- stair_distances.reserve( stairs.size() * stairs.size() );
+ const int nstairs = stairs.size();
+ stair_distances.reserve( nstairs * nstairs );
+ stair_distances.resize( nstairs * nstairs, 0 );
}
int LevelInfo::distance_between(const stair_info *s1, const stair_info *s2)
@@ -2829,7 +2831,7 @@ int LevelInfo::distance_between(const stair_info *s1, const stair_info *s2)
int i1 = get_stair_index(s1->position),
i2 = get_stair_index(s2->position);
if (i1 == -1 || i2 == -1) return 0;
-
+
return stair_distances[ i1 * stairs.size() + i2 ];
}
@@ -2887,9 +2889,10 @@ void LevelInfo::save(FILE *file) const
if (stair_count)
{
// Save stair distances as short ints.
- for (int i = stair_count * stair_count - 1; i >= 0; --i)
+ const int sz = stair_distances.size();
+ for (int i = 0, n = stair_count * stair_count; i < n; ++i)
{
- if (i >= (int) stair_distances.size())
+ if (i >= sz)
writeShort(file, -1);
else
writeShort(file, stair_distances[i]);