summaryrefslogtreecommitdiffstats
path: root/trunk
diff options
context:
space:
mode:
authordshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2006-08-02 12:54:15 +0000
committerdshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573>2006-08-02 12:54:15 +0000
commitd5e5340c3926d1cf97f6cba151ffaecb20bfb35f (patch)
treed1faf7d5b27df8f3c523a8dd33357804118e62b1 /trunk
parent7b2204d69f21d7075e4666ee032d7a129081bc4b (diff)
downloadcrawl-ref-d5e5340c3926d1cf97f6cba151ffaecb20bfb35f.tar.gz
crawl-ref-d5e5340c3926d1cf97f6cba151ffaecb20bfb35f.zip
Integrated travel patch as of 20060727
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@7 c06c8d41-db1a-0410-9941-cceddc491573
Diffstat (limited to 'trunk')
-rw-r--r--trunk/source/AppHdr.h54
-rw-r--r--trunk/source/FixAry.h5
-rw-r--r--trunk/source/Kills.cc1098
-rw-r--r--trunk/source/Kills.h186
-rw-r--r--trunk/source/abl-show.cc13
-rw-r--r--trunk/source/abl-show.h3
-rw-r--r--trunk/source/acr.cc142
-rw-r--r--trunk/source/beam.cc2
-rw-r--r--trunk/source/chardump.cc309
-rw-r--r--trunk/source/chardump.h4
-rw-r--r--trunk/source/clua.cc2330
-rwxr-xr-xtrunk/source/clua.h122
-rw-r--r--trunk/source/command.cc104
-rw-r--r--trunk/source/command.h2
-rw-r--r--trunk/source/debug.cc15
-rw-r--r--trunk/source/decks.cc8
-rw-r--r--trunk/source/defines.h34
-rw-r--r--trunk/source/describe.cc159
-rw-r--r--trunk/source/describe.h6
-rw-r--r--trunk/source/direct.cc1048
-rw-r--r--trunk/source/direct.h6
-rw-r--r--trunk/source/dungeon.cc17
-rw-r--r--trunk/source/effects.cc19
-rw-r--r--trunk/source/effects.h2
-rw-r--r--trunk/source/enum.h105
-rw-r--r--trunk/source/externs.h278
-rw-r--r--trunk/source/fight.cc8
-rw-r--r--trunk/source/files.cc291
-rw-r--r--trunk/source/files.h27
-rw-r--r--trunk/source/food.cc217
-rw-r--r--trunk/source/food.h13
-rw-r--r--trunk/source/hiscores.cc3
-rw-r--r--trunk/source/initfile.cc1433
-rw-r--r--trunk/source/initfile.h63
-rw-r--r--trunk/source/invent.cc802
-rw-r--r--trunk/source/invent.h47
-rw-r--r--trunk/source/item_use.cc296
-rw-r--r--trunk/source/item_use.h16
-rw-r--r--trunk/source/itemname.cc64
-rw-r--r--trunk/source/itemname.h1
-rw-r--r--trunk/source/items.cc681
-rw-r--r--trunk/source/items.h21
-rw-r--r--trunk/source/libmac.h5
-rw-r--r--trunk/source/libunix.cc84
-rw-r--r--trunk/source/libunix.h2
-rw-r--r--trunk/source/libutil.cc635
-rw-r--r--trunk/source/libutil.h193
-rw-r--r--trunk/source/libw32c.cc137
-rw-r--r--trunk/source/libw32c.h3
-rw-r--r--trunk/source/lua/base.lua74
-rw-r--r--trunk/source/lua/chnkdata.lua35
-rw-r--r--trunk/source/lua/eat.lua97
-rw-r--r--trunk/source/lua/gearset.lua206
-rw-r--r--trunk/source/lua/gourmand.lua145
-rw-r--r--trunk/source/lua/kills.lua240
-rw-r--r--trunk/source/lua/runrest.lua108
-rw-r--r--trunk/source/lua/safechunk.lua41
-rw-r--r--trunk/source/lua/stash.lua32
-rw-r--r--trunk/source/lua/wield.lua47
-rw-r--r--trunk/source/macro.cc454
-rw-r--r--trunk/source/macro.h25
-rw-r--r--trunk/source/makefile.lnx8
-rwxr-xr-xtrunk/source/makefile.mgw51
-rw-r--r--trunk/source/makefile.obj8
-rw-r--r--trunk/source/menu.cc501
-rwxr-xr-xtrunk/source/menu.h216
-rw-r--r--trunk/source/message.cc76
-rw-r--r--trunk/source/message.h6
-rw-r--r--trunk/source/misc.cc137
-rw-r--r--trunk/source/misc.h2
-rw-r--r--trunk/source/mon-util.cc21
-rw-r--r--trunk/source/mon-util.h47
-rw-r--r--trunk/source/monplace.cc20
-rw-r--r--trunk/source/monplace.h6
-rw-r--r--trunk/source/monstuff.cc60
-rw-r--r--trunk/source/mt19937ar.cc229
-rw-r--r--trunk/source/mt19937ar.h52
-rw-r--r--trunk/source/newgame.cc889
-rw-r--r--trunk/source/ouch.cc23
-rw-r--r--trunk/source/output.cc279
-rw-r--r--trunk/source/output.h10
-rw-r--r--trunk/source/player.cc262
-rw-r--r--trunk/source/player.h28
-rw-r--r--trunk/source/randart.cc47
-rw-r--r--trunk/source/religion.cc17
-rw-r--r--trunk/source/shopping.cc113
-rw-r--r--trunk/source/shopping.h10
-rw-r--r--trunk/source/skills.cc3
-rw-r--r--trunk/source/spells2.cc43
-rw-r--r--trunk/source/spells3.cc4
-rw-r--r--trunk/source/spells4.cc2
-rw-r--r--trunk/source/spl-book.cc108
-rw-r--r--trunk/source/spl-book.h5
-rw-r--r--trunk/source/spl-cast.cc27
-rw-r--r--trunk/source/stash.cc1606
-rw-r--r--trunk/source/stash.h327
-rw-r--r--trunk/source/stuff.cc254
-rw-r--r--trunk/source/stuff.h15
-rw-r--r--trunk/source/tags.cc58
-rw-r--r--trunk/source/tags.h2
-rwxr-xr-xtrunk/source/travel.cc2923
-rw-r--r--trunk/source/travel.h369
-rw-r--r--trunk/source/unrand.h26
-rw-r--r--trunk/source/version.h2
-rw-r--r--trunk/source/view.cc998
-rw-r--r--trunk/source/view.h11
106 files changed, 20221 insertions, 2297 deletions
diff --git a/trunk/source/AppHdr.h b/trunk/source/AppHdr.h
index 426ba93a39..44c115b80a 100644
--- a/trunk/source/AppHdr.h
+++ b/trunk/source/AppHdr.h
@@ -45,6 +45,18 @@
#pragma message("Compiling AppHeader.h (this message should only appear once)")
#endif
+#if defined(GCC)
+# define HASH_CONTAINER_NS __gnu_cxx
+# define HASH_CONTAINERS
+#endif
+
+// Enables stash-tracking: keeps track of items in the dungeon as a convenience
+// for the player.
+#define STASH_TRACKING
+
+// Uncomment to enable the Crawl Lua bindings.
+//
+// #define CLUA_BINDINGS
// =========================================================================
// System Defines
@@ -88,6 +100,37 @@
// term used... under X Windows try "rxvt".
#define USE_COLOUR_OPTS
+ // More sophisticated character handling
+ #define CURSES_USE_KEYPAD
+
+ // How long (milliseconds) curses should wait for additional characters
+ // after seeing an Escape (0x1b) keypress. May not be available on all
+ // curses implementations.
+ #define CURSES_SET_ESCDELAY 20
+
+ // Use this to seed the PRNG with a bit more than just time()... turning
+ // this off is perfectly okay, the game just becomes more exploitable
+ // with a bit of hacking (ie only by people who know how).
+ //
+ // For now, we'll make it default to on for Linux (who should have
+ // no problems with compiling this).
+ #define USE_MORE_SECURE_SEED
+
+ // Use POSIX regular expressions
+ #define REGEX_POSIX
+
+ // If you have libpcre, you can use that instead of POSIX regexes -
+ // uncomment the line below and add -lpcre to your makefile.
+ // #define REGEX_PCRE
+
+ // Uncomment (and edit as appropriate) to play sounds.
+ //
+ // WARNING: Enabling sounds may compromise security if Crawl is installed
+ // setuid or setgid. Filenames passed to this command *are not
+ // validated in any way*.
+ //
+ // #define SOUND_PLAY_COMMAND "/usr/bin/play -v .5 %s 2>/dev/null &"
+
// For cases when the game will be played on terms that don't support the
// curses "bold == lighter" 16 colour mode. -- bwr
//
@@ -128,7 +171,7 @@
#elif defined(DOS)
#define DOS_TERM
#define SHORT_FILE_NAMES
- #define EOL "\n\r"
+ #define EOL "\r\n"
#define CHARACTER_SET A_ALTCHARSET
#include <string>
@@ -137,13 +180,20 @@
#define NEED_SNPRINTF
#endif
-#elif defined(WIN32CONSOLE) && (defined(__IBMCPP__) || defined(__BCPLUSPLUS__))
+#elif defined(WIN32CONSOLE) && (defined(__IBMCPP__) || defined(__BCPLUSPLUS__) || defined(__MINGW32__))
#include "libw32c.h"
#define PLAIN_TERM
#define SHORT_FILE_NAMES
#define EOL "\n"
#define CHARACTER_SET A_ALTCHARSET
#define getstr(X,Y) getConsoleString(X,Y)
+
+ // Uncomment to play sounds. winmm must be linked in if this is uncommented.
+ // #define WINMM_PLAY_SOUNDS
+
+ // Use Perl-compatible regular expressions. libpcre must be available and
+ // linked in.
+ // #define REGEX_PCRE
#else
#error unsupported compiler
#endif
diff --git a/trunk/source/FixAry.h b/trunk/source/FixAry.h
index 58db5bb58d..99f802e2f3 100644
--- a/trunk/source/FixAry.h
+++ b/trunk/source/FixAry.h
@@ -42,11 +42,6 @@ public:
FixedArray() {}
-private:
- FixedArray(const FixedArray& rhs);
-
- FixedArray& operator=(const FixedArray& rhs);
-
//-----------------------------------
// API
//
diff --git a/trunk/source/Kills.cc b/trunk/source/Kills.cc
new file mode 100644
index 0000000000..4a1eaa0ebf
--- /dev/null
+++ b/trunk/source/Kills.cc
@@ -0,0 +1,1098 @@
+/*
+ * File: Kills.cc
+ * Summary: Player kill tracking
+ * Written by: Darshan Shaligram
+ */
+#include "AppHdr.h"
+#include "chardump.h"
+#include "describe.h"
+#include "mon-util.h"
+#include "files.h"
+#include "itemname.h"
+#include "travel.h"
+#include "tags.h"
+#include "Kills.h"
+#include "clua.h"
+#include <algorithm>
+
+#define KILLS_MAJOR_VERSION 4
+#define KILLS_MINOR_VERSION 1
+
+#ifdef CLUA_BINDINGS
+static void kill_lua_filltable(std::vector<kill_exp> &v);
+#endif
+
+
+unsigned short get_packed_place( unsigned char branch, int subdepth,
+ char level_type )
+{
+ unsigned short place = (unsigned short)
+ ( (branch << 8) | subdepth );
+ if (level_type == LEVEL_ABYSS || level_type == LEVEL_PANDEMONIUM
+ || level_type == LEVEL_LABYRINTH)
+ place = (unsigned short) ( (level_type << 8) | 0xFF );
+ return place;
+}
+
+unsigned short get_packed_place()
+{
+ return get_packed_place( you.where_are_you,
+ subdungeon_depth(you.where_are_you, you.your_level),
+ you.level_type );
+}
+
+///////////////////////////////////////////////////////////////////////////
+// KillMaster
+//
+
+const char *kill_category_names[] =
+{
+ "you",
+ "collateral kills",
+ "others",
+};
+
+const char *KillMaster::category_name(KillCategory kc) const
+{
+ if (kc >= KC_YOU && kc < KC_NCATEGORIES)
+ return (kill_category_names[kc]);
+ return (NULL);
+}
+
+bool KillMaster::empty() const
+{
+ for (int i = 0; i < KC_NCATEGORIES; ++i)
+ if (!categorized_kills[i].empty())
+ return (false);
+ return (true);
+}
+
+void KillMaster::save(FILE *file) const
+{
+ // Write the version of the kills file
+ writeByte(file, KILLS_MAJOR_VERSION);
+ writeByte(file, KILLS_MINOR_VERSION);
+
+ for (int i = 0; i < KC_NCATEGORIES; ++i)
+ categorized_kills[i].save(file);
+}
+
+void KillMaster::load(FILE *file)
+{
+ unsigned char major = readByte(file),
+ minor = readByte(file);
+ if (major != KILLS_MAJOR_VERSION ||
+ (minor != KILLS_MINOR_VERSION && minor > 0))
+ return ;
+
+ for (int i = 0; i < KC_NCATEGORIES; ++i)
+ {
+ categorized_kills[i].load(file);
+ if (!minor)
+ break;
+ }
+}
+
+void KillMaster::record_kill(const monsters *mon, int killer, bool ispet)
+{
+ KillCategory kc =
+ (killer == KILL_YOU || killer == KILL_YOU_MISSILE)? KC_YOU :
+ (ispet)? KC_FRIENDLY :
+ KC_OTHER;
+ categorized_kills[kc].record_kill(mon);
+}
+
+std::string KillMaster::kill_info() const
+{
+ if (empty())
+ return ("");
+
+ std::string killtext;
+
+ bool needseparator = false;
+ int categories = 0;
+ long grandtotal = 0L;
+
+ Kills catkills[KC_NCATEGORIES];
+ for (int i = 0; i < KC_NCATEGORIES; ++i)
+ {
+ int targ = Options.kill_map[i];
+ catkills[targ].merge( categorized_kills[i] );
+ }
+
+ for (int i = KC_YOU; i < KC_NCATEGORIES; ++i)
+ {
+ if (catkills[i].empty())
+ continue;
+
+ categories++;
+ std::vector<kill_exp> kills;
+ long count = catkills[i].get_kills(kills);
+ grandtotal += count;
+
+ add_kill_info( killtext,
+ kills,
+ count,
+ i == KC_YOU? NULL :
+ category_name((KillCategory) i),
+ needseparator );
+ needseparator = true;
+ }
+
+ std::string grandt;
+ if (categories > 1)
+ {
+ char buf[200];
+ snprintf(buf, sizeof buf,
+ "Grand Total: %ld creatures vanquished",
+ grandtotal);
+ grandt = buf;
+ }
+
+#ifdef CLUA_BINDINGS
+ // Call the kill dump Lua function with null a, to tell it we're done.
+ if (!clua.callfn("c_kill_list", "ss", NULL, grandt.c_str()))
+#endif
+ {
+ // We can sum up ourselves, if Lua doesn't want to.
+ // FIXME: I'm not happy with the looks/wording of the grand total
+ // count.
+ if (categories > 1)
+ {
+ // Give ourselves a newline first
+ killtext += EOL;
+ killtext += grandt + EOL;
+ }
+ }
+
+ return killtext;
+}
+
+void KillMaster::add_kill_info(std::string &killtext,
+ std::vector<kill_exp> &kills,
+ long count,
+ const char *category,
+ bool separator) const
+{
+#ifdef CLUA_BINDINGS
+ // Set a pointer to killtext as a Lua global
+ lua_pushlightuserdata(clua.state(), &killtext);
+ clua.setregistry("cr_skill");
+
+ // Populate a Lua table with kill_exp structs, in the default order,
+ // and leave the table on the top of the Lua stack.
+ kill_lua_filltable(kills);
+
+ if (category)
+ lua_pushstring(clua, category);
+ else
+ lua_pushnil(clua);
+
+ lua_pushboolean(clua, separator);
+
+ if (!clua.callfn("c_kill_list", 3, 0))
+#endif
+ {
+#ifdef CLUA_BINDINGS
+ if (clua.error.length())
+ {
+ killtext += "Lua error:\n";
+ killtext += clua.error + "\n\n";
+ }
+#endif
+ if (separator)
+ killtext += EOL;
+
+ killtext += "Vanquished Creatures";
+ if (category)
+ killtext += std::string(" (") + category + ")";
+
+ killtext += EOL;
+
+ for (int i = 0, sz = kills.size(); i < sz; ++i)
+ {
+ killtext += " " + kills[i].desc;
+ killtext += EOL;
+ }
+ {
+ char numbuf[100];
+ snprintf(numbuf, sizeof numbuf,
+ "%ld creature%s vanquished." EOL, count,
+ count > 1? "s" : "");
+ killtext += numbuf;
+ }
+ }
+}
+
+///////////////////////////////////////////////////////////////////////////
+
+bool Kills::empty() const
+{
+ return kills.empty() && ghosts.empty();
+}
+
+void Kills::merge(const Kills &k)
+{
+ ghosts.insert( ghosts.end(), k.ghosts.begin(), k.ghosts.end() );
+
+ // Regular kills are messier to merge.
+ for (kill_map::const_iterator i = k.kills.begin();
+ i != k.kills.end(); ++i)
+ {
+ const kill_monster_desc &kmd = i->first;
+ kill_def &k = kills[kmd];
+ const kill_def &ko = i->second;
+ bool uniq = mons_is_unique(kmd.monnum);
+ k.merge(ko, uniq);
+ }
+}
+
+void Kills::record_kill(const struct monsters *mon)
+{
+ // Handle player ghosts separately.
+ if (mon->type == MONS_PLAYER_GHOST || mon->type == MONS_PANDEMONIUM_DEMON)
+ {
+ record_ghost_kill(mon);
+ return ;
+ }
+
+ // Normal monsters
+ // Create a descriptor
+ kill_monster_desc descriptor = mon;
+
+ kill_def &k = kills[descriptor];
+ if (k.kills)
+ k.add_kill(mon, get_packed_place());
+ else
+ k = kill_def(mon);
+}
+
+long Kills::get_kills(std::vector<kill_exp> &all_kills) const
+{
+ long count = 0;
+ kill_map::const_iterator iter = kills.begin();
+ for (; iter != kills.end(); ++iter)
+ {
+ const kill_monster_desc &md = iter->first;
+ const kill_def &k = iter->second;
+ all_kills.push_back( kill_exp(k, md) );
+ count += k.kills;
+ }
+
+ ghost_vec::const_iterator gi = ghosts.begin();
+ for (; gi != ghosts.end(); ++gi)
+ {
+ all_kills.push_back( kill_exp(*gi) );
+ }
+ count += ghosts.size();
+
+ std::sort(all_kills.begin(), all_kills.end());
+ return (count);
+}
+
+// Takes a packed 'place' and returns a compact stringified place name.
+// XXX: This is done in several other places; a unified function to
+// describe places would be nice.
+std::string short_place_name(unsigned short place)
+{
+ unsigned char branch = (unsigned char) ((place >> 8) & 0xFF);
+ int lev = place & 0xFF;
+
+ const char *s;
+ bool level_num = false;
+ if (lev == 0xFF)
+ {
+ switch (branch)
+ {
+ case LEVEL_ABYSS:
+ s = "Abyss";
+ break;
+ case LEVEL_PANDEMONIUM:
+ s = "Pan";
+ break;
+ case LEVEL_LABYRINTH:
+ s = "Lab";
+ break;
+ default:
+ s = "Buggy Badlands";
+ break;
+ }
+ }
+ else
+ {
+ switch (branch)
+ {
+ case BRANCH_VESTIBULE_OF_HELL:
+ s = "Hell";
+ break;
+ case BRANCH_HALL_OF_BLADES:
+ s = "Blade";
+ break;
+ case BRANCH_ECUMENICAL_TEMPLE:
+ s = "Temple";
+ break;
+ default:
+ level_num = true;
+ s = (branch == BRANCH_DIS) ? "Dis:" :
+ (branch == BRANCH_GEHENNA) ? "Geh:" :
+ (branch == BRANCH_COCYTUS) ? "Coc:" :
+ (branch == BRANCH_TARTARUS) ? "Tar:" :
+ (branch == BRANCH_ORCISH_MINES) ? "Orc:" :
+ (branch == BRANCH_HIVE) ? "Hive:" :
+ (branch == BRANCH_LAIR) ? "Lair:" :
+ (branch == BRANCH_SLIME_PITS) ? "Slime:" :
+ (branch == BRANCH_VAULTS) ? "Vault:" :
+ (branch == BRANCH_CRYPT) ? "Crypt:" :
+ (branch == BRANCH_HALL_OF_ZOT) ? "Zot:" :
+ (branch == BRANCH_SNAKE_PIT) ? "Snake:" :
+ (branch == BRANCH_ELVEN_HALLS) ? "Elf:" :
+ (branch == BRANCH_TOMB) ? "Tomb:" :
+ (branch == BRANCH_SWAMP) ? "Swamp:" : "D:";
+ break;
+ }
+ }
+
+ std::string pl = s;
+ if (level_num)
+ {
+ char buf[20];
+ snprintf(buf, sizeof buf, "%d", lev);
+ pl += buf;
+ }
+ return pl;
+}
+
+void Kills::save(FILE *file) const
+{
+ // How many kill records do we have?
+ writeLong(file, kills.size());
+
+ kill_map::const_iterator iter = kills.begin();
+ for ( ; iter != kills.end(); ++iter)
+ {
+ iter->first.save(file);
+ iter->second.save(file);
+ }
+
+ // How many ghosts do we have?
+ writeShort(file, ghosts.size());
+ for (ghost_vec::const_iterator iter = ghosts.begin();
+ iter != ghosts.end(); ++iter)
+ {
+ iter->save(file);
+ }
+}
+
+void Kills::load(FILE *file)
+{
+ // How many kill records?
+ long kill_count = readLong(file);
+ kills.clear();
+ for (long i = 0; i < kill_count; ++i)
+ {
+ kill_monster_desc md;
+ md.load(file);
+ kills[md].load(file);
+ }
+
+ short ghost_count = readShort(file);
+ ghosts.clear();
+ for (short i = 0; i < ghost_count; ++i)
+ {
+ kill_ghost kg;
+ kg.load(file);
+ ghosts.push_back(kg);
+ }
+}
+
+void Kills::record_ghost_kill(const struct monsters *mon)
+{
+ kill_ghost ghost(mon);
+ ghosts.push_back(ghost);
+}
+
+kill_def::kill_def(const struct monsters *mon) : kills(0), exp(0)
+{
+ exp = exper_value( (struct monsters *) mon);
+ add_kill(mon, get_packed_place());
+}
+
+static bool ends_with(const std::string &s, const char *suffix)
+{
+ std::string other = suffix;
+ if (s.length() < other.length()) return false;
+ return (s.substr(s.length() - other.length()) == other);
+}
+
+static bool ends_with(const std::string &s, const char *suffixes[])
+{
+ if (!suffixes) return false;
+ for ( ; *suffixes; suffixes++)
+ {
+ if (ends_with(s, *suffixes))
+ return true;
+ }
+ return false;
+}
+
+// For monster names ending with these suffixes, we pluralize directly without
+// attempting to use the "of" rule. For instance:
+//
+// moth of wrath => moths of wrath but
+// moth of wrath zombie => moth of wrath zombies.
+//
+// This is not necessary right now, since there are currently no monsters that
+// require this special treatment (no monster with 'of' in its name is eligible
+// for zombies or skeletons).
+static const char *modifier_suffixes[] =
+{
+ "zombie", "skeleton", "simulacrum", NULL,
+};
+
+// Pluralizes a monster name. This'll need to be updated for correctness
+// whenever new monsters are added.
+static std::string pluralize(const std::string &name,
+ const char *no_of[] = NULL)
+{
+ std::string::size_type pos;
+
+ // Pluralize first word of names like 'eye of draining', but only if the
+ // whole name is not suffixed by a modifier, such as 'zombie' or 'skeleton'
+ if ( (pos = name.find(" of ")) != std::string::npos
+ && !ends_with(name, no_of) )
+ {
+ return pluralize(name.substr(0, pos)) + name.substr(pos);
+ }
+ else if (ends_with(name, "us"))
+ // Fungus, ufetubus, for instance.
+ return name.substr(0, name.length() - 2) + "i";
+ else if (ends_with(name, "larva") || ends_with(name, "amoeba"))
+ // Giant amoebae sounds a little weird, to tell the truth.
+ return name + "e";
+ else if (ends_with(name, "ex"))
+ // Vortex; vortexes is legal, but the classic plural is cooler.
+ return name.substr(0, name.length() - 2) + "ices";
+ else if (ends_with(name, "cyclops"))
+ return name.substr(0, name.length() - 1) + "es";
+ else if (ends_with(name, "y"))
+ return name.substr(0, name.length() - 1) + "ies";
+ else if (ends_with(name, "lf"))
+ // Elf, wolf. Dwarfs can stay dwarfs, if there were dwarfs.
+ return name.substr(0, name.length() - 1) + "ves";
+ else if (ends_with(name, "mage"))
+ // mage -> magi
+ return name.substr(0, name.length() - 1) + "i";
+ else if ( ends_with(name, "sheep") || ends_with(name, "manes")
+ || ends_with(name, "fish") )
+ // Maybe we should generalise 'manes' to ends_with("es")?
+ return name;
+ else if (ends_with(name, "ch") || ends_with(name, "sh")
+ || ends_with(name, "x"))
+ // To handle cockroaches, fish and sphinxes. Fish will be netted by
+ // the previous check anyway.
+ return name + "es";
+ else if (ends_with(name, "um"))
+ // simulacrum -> simulacra
+ return name.substr(0, name.length() - 2) + "a";
+ else if (ends_with(name, "efreet"))
+ // efreet -> efreeti. Not sure this is correct.
+ return name + "i";
+
+ return name + "s";
+}
+
+// Naively prefix A/an to a monster name. At the moment, we don't have monster
+// names that demand more sophistication (maybe ynoxinul - don't know how
+// that's pronounced).
+static std::string article_a(const std::string &name)
+{
+ if (!name.length()) return name;
+ switch (name[0])
+ {
+ case 'a': case 'e': case 'i': case 'o': case 'u':
+ case 'A': case 'E': case 'I': case 'O': case 'U':
+ return "An " + name;
+ default:
+ return "A " + name;
+ }
+}
+
+// For a non-unique monster, prefixes a suitable article if we have only one
+// kill, else prefixes a kill count and pluralizes the monster name.
+static std::string n_names(const std::string &name, int n)
+{
+ if (n > 1)
+ {
+ char buf[20];
+ snprintf(buf, sizeof buf, "%d ", n);
+ return buf + pluralize(name, modifier_suffixes);
+ }
+ else
+ return article_a(name);
+}
+
+// Returns a string describing the number of times a unique has been killed.
+// Currently required only for Boris.
+//
+static std::string kill_times(int kills)
+{
+ char buf[50];
+ switch (kills)
+ {
+ case 1:
+ strcpy(buf, " (once)");
+ break;
+ case 2:
+ strcpy(buf, " (twice)");
+ break;
+ case 3:
+ strcpy(buf, " (thrice)");
+ break;
+ default:
+ snprintf(buf, sizeof buf, " (%d times)", kills);
+ break;
+ }
+ return std::string(buf);
+}
+
+void kill_def::merge(const kill_def &k, bool uniq)
+{
+ if (!kills)
+ {
+ *this = k;
+ }
+ else
+ {
+ kills += k.kills;
+ for (int i = 0, size = k.places.size(); i < size; ++i)
+ add_place(k.places[i], uniq);
+ }
+}
+
+void kill_def::add_kill(const struct monsters *mon, unsigned short place)
+{
+ kills++;
+ add_place(place, mons_is_unique(mon->type));
+}
+
+void kill_def::add_place(unsigned short place, bool force)
+{
+ for (unsigned i = 0; i < places.size(); ++i)
+ if (places[i] == place) return;
+
+ if (force || places.size() < PLACE_LIMIT)
+ places.push_back(place);
+}
+
+std::string kill_def::base_name(const kill_monster_desc &md) const
+{
+ char monnamebuf[ITEMNAME_SIZE]; // Le sigh.
+ moname(md.monnum, true, DESC_PLAIN, monnamebuf);
+
+ std::string name = monnamebuf;
+ switch (md.modifier)
+ {
+ case kill_monster_desc::M_ZOMBIE:
+ name += " zombie";
+ break;
+ case kill_monster_desc::M_SKELETON:
+ name += " skeleton";
+ break;
+ case kill_monster_desc::M_SIMULACRUM:
+ name += " simulacrum";
+ break;
+ case kill_monster_desc::M_SPECTRE:
+ name = "spectral " + name;
+ break;
+ default:
+ // Silence compiler warning about not handling M_NORMAL and
+ // M_SHAPESHIFTER
+ break;
+ }
+
+ switch (md.monnum)
+ {
+ case MONS_ABOMINATION_LARGE:
+ name = "large " + name;
+ break;
+ case MONS_ABOMINATION_SMALL:
+ // Do nothing
+ break;
+ case MONS_RAKSHASA_FAKE:
+ name = "illusory " + name;
+ break;
+ }
+ return name;
+}
+
+std::string kill_def::info(const kill_monster_desc &md) const
+{
+ std::string name = base_name(md);
+
+ if (!mons_is_unique(md.monnum))
+ {
+ // Pluralize as needed
+ name = n_names(name, kills);
+
+ // We brand shapeshifters with the (shapeshifter) qualifier. This
+ // has to be done after doing pluralize(), else we get very odd plurals
+ // :)
+ if (md.modifier == kill_monster_desc::M_SHAPESHIFTER &&
+ md.monnum != MONS_SHAPESHIFTER &&
+ md.monnum != MONS_GLOWING_SHAPESHIFTER)
+ name += " (shapeshifter)";
+ }
+ else if (kills > 1)
+ {
+ // Aha! A resurrected unique
+ name += kill_times(kills);
+ }
+
+ // What places we killed this type of monster
+ return append_places(md, name);
+}
+
+std::string kill_def::append_places(const kill_monster_desc &md,
+ const std::string &name) const
+{
+ if (Options.dump_kill_places == KDO_NO_PLACES) return name;
+
+ int nplaces = places.size();
+ if ( nplaces == 1 || mons_is_unique(md.monnum)
+ || Options.dump_kill_places == KDO_ALL_PLACES )
+ {
+ std::string augmented = name;
+ augmented += " (";
+ for (std::vector<unsigned short>::const_iterator iter = places.begin();
+ iter != places.end(); ++iter)
+ {
+ if (iter != places.begin())
+ augmented += " ";
+ augmented += short_place_name(*iter);
+ }
+ augmented += ")";
+ return augmented;
+ }
+ return name;
+}
+
+void kill_def::save(FILE *file) const
+{
+ writeShort(file, kills);
+ writeShort(file, exp);
+
+ writeShort(file, places.size());
+ for (std::vector<unsigned short>::const_iterator iter = places.begin();
+ iter != places.end(); ++iter)
+ {
+ writeShort(file, *iter);
+ }
+}
+
+void kill_def::load(FILE *file)
+{
+ kills = (unsigned short) readShort(file);
+ exp = readShort(file);
+
+ places.clear();
+ short place_count = readShort(file);
+ for (short i = 0; i < place_count; ++i)
+ {
+ places.push_back((unsigned short) readShort(file));
+ }
+}
+
+kill_ghost::kill_ghost(const struct monsters *mon)
+{
+ exp = exper_value( (struct monsters *) mon);
+ place = get_packed_place();
+ ghost_name = ghost.name;
+
+ // Check whether this is really a ghost, since we also have to handle
+ // the Pandemonic demons.
+ if (mon->type == MONS_PLAYER_GHOST)
+ ghost_name = "The ghost of " + ghost_description(true);
+}
+
+std::string kill_ghost::info() const
+{
+ return ghost_name +
+ (Options.dump_kill_places != KDO_NO_PLACES?
+ " (" + short_place_name(place) + ")" : std::string(""));
+}
+
+void kill_ghost::save(FILE *file) const
+{
+ writeString(file, ghost_name);
+ writeShort(file, (unsigned short) exp);
+ writeShort(file, place);
+}
+
+void kill_ghost::load(FILE *file)
+{
+ ghost_name = readString(file);
+ exp = readShort(file);
+ place = (unsigned short) readShort(file);
+}
+
+kill_monster_desc::kill_monster_desc(const monsters *mon)
+{
+
+ // TODO: We need to understand how shapeshifters are handled.
+ monnum = mon->type;
+ modifier = M_NORMAL;
+ switch (mon->type)
+ {
+ case MONS_ZOMBIE_LARGE: case MONS_ZOMBIE_SMALL:
+ modifier = M_ZOMBIE;
+ break;
+ case MONS_SKELETON_LARGE: case MONS_SKELETON_SMALL:
+ modifier = M_SKELETON;
+ break;
+ case MONS_SIMULACRUM_LARGE: case MONS_SIMULACRUM_SMALL:
+ modifier = M_SIMULACRUM;
+ break;
+ case MONS_SPECTRAL_THING:
+ modifier = M_SPECTRE;
+ break;
+ }
+ if (modifier != M_NORMAL) monnum = mon->number;
+
+ if (mons_has_ench((struct monsters *) mon, ENCH_SHAPESHIFTER) ||
+ mons_has_ench((struct monsters *) mon, ENCH_GLOWING_SHAPESHIFTER))
+ modifier = M_SHAPESHIFTER;
+
+ // XXX: Ugly hack - merge all mimics into one mimic record.
+ if (monnum >= MONS_GOLD_MIMIC && monnum <= MONS_POTION_MIMIC)
+ monnum = MONS_WEAPON_MIMIC;
+}
+
+void kill_monster_desc::save(FILE *file) const
+{
+ writeShort(file, (short) monnum);
+ writeShort(file, (short) modifier);
+}
+
+void kill_monster_desc::load(FILE *file)
+{
+ monnum = (int) readShort(file);
+ modifier = (name_modifier) readShort(file);
+}
+
+#ifdef CLUA_BINDINGS
+///////////////////////////////////////////////////////////////////////////
+// Kill Lua interface
+//
+
+#define KILLEXP_ACCESS(name, type, field) \
+ static int kill_lualc_##name(lua_State *ls) { \
+ if (!lua_islightuserdata(ls, 1)) { \
+ luaL_argerror(ls, 1, "Unexpected argument type"); \
+ return 0; \
+ } \
+ \
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) ); \
+ if (ke) { \
+ lua_push##type(ls, ke->field); \
+ return 1; \
+ } \
+ return 0; \
+ }
+
+KILLEXP_ACCESS(nkills, number, nkills)
+KILLEXP_ACCESS(exp, number, exp)
+KILLEXP_ACCESS(base_name, string, base_name.c_str())
+KILLEXP_ACCESS(desc, string, desc.c_str())
+KILLEXP_ACCESS(monnum, number, monnum)
+KILLEXP_ACCESS(isghost, boolean,
+ monnum == -1 &&
+ ke->desc.find("The ghost of") != std::string::npos)
+KILLEXP_ACCESS(ispandemon, boolean,
+ monnum == -1 &&
+ ke->desc.find("The ghost of") == std::string::npos)
+KILLEXP_ACCESS(isunique, boolean,
+ monnum != -1 && mons_is_unique(ke->monnum))
+
+
+static int kill_lualc_modifier(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
+ if (ke)
+ {
+ const char *modifier;
+ switch (ke->modifier)
+ {
+ case kill_monster_desc::M_ZOMBIE:
+ modifier = "zombie";
+ break;
+ case kill_monster_desc::M_SKELETON:
+ modifier = "skeleton";
+ break;
+ case kill_monster_desc::M_SIMULACRUM:
+ modifier = "simulacrum";
+ break;
+ case kill_monster_desc::M_SPECTRE:
+ modifier = "spectre";
+ break;
+ case kill_monster_desc::M_SHAPESHIFTER:
+ modifier = "shapeshifter";
+ break;
+ default:
+ modifier = "";
+ break;
+ }
+ lua_pushstring(ls, modifier);
+ return 1;
+ }
+ return 0;
+}
+
+static int kill_lualc_places(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
+ if (ke)
+ {
+ lua_newtable(ls);
+ for (int i = 0, count = ke->places.size(); i < count; ++i)
+ {
+ lua_pushnumber(ls, ke->places[i]);
+ lua_rawseti(ls, -2, i + 1);
+ }
+ return 1;
+ }
+ return 0;
+}
+
+static int kill_lualc_place_name(lua_State *ls)
+{
+ int num = luaL_checkint(ls, 1);
+ std::string plname = short_place_name(num);
+ lua_pushstring(ls, plname.c_str());
+ return 1;
+}
+
+static bool is_ghost(const kill_exp *ke)
+{
+ return ke->monnum == -1
+ && ke->desc.find("The ghost of") != std::string::npos;
+}
+
+static int kill_lualc_holiness(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
+ if (ke)
+ {
+ const char *verdict = "strange";
+ if (ke->monnum == -1)
+ {
+ verdict = is_ghost(ke)? "undead" : "demonic";
+ }
+ else
+ {
+ switch (mons_holiness(ke->monnum))
+ {
+ case MH_HOLY: verdict = "holy"; break;
+ case MH_NATURAL: verdict = "natural"; break;
+ case MH_UNDEAD: verdict = "undead"; break;
+ case MH_DEMONIC: verdict = "demonic"; break;
+ case MH_NONLIVING: verdict = "nonliving"; break;
+ case MH_PLANT: verdict = "plant"; break;
+ }
+ if (ke->modifier != kill_monster_desc::M_NORMAL
+ && ke->modifier != kill_monster_desc::M_SHAPESHIFTER)
+ verdict = "undead";
+ }
+ lua_pushstring(ls, verdict);
+ return 1;
+ }
+ return 0;
+}
+
+static int kill_lualc_symbol(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
+ if (ke)
+ {
+ unsigned char ch = ke->monnum != -1?
+ mons_char(ke->monnum) :
+ is_ghost(ke)? 'p' : '&';
+
+ if (ke->monnum == MONS_PROGRAM_BUG)
+ ch = ' ';
+
+ switch (ke->modifier)
+ {
+ case kill_monster_desc::M_ZOMBIE:
+ case kill_monster_desc::M_SKELETON:
+ case kill_monster_desc::M_SIMULACRUM:
+ ch = mons_zombie_size(ke->monnum) == Z_SMALL? 'z' : 'Z';
+ break;
+ case kill_monster_desc::M_SPECTRE:
+ ch = 'W';
+ break;
+ default:
+ break;
+ }
+
+ char s[2];
+ s[0] = (char) ch;
+ s[1] = 0;
+ lua_pushstring(ls, s);
+ return 1;
+ }
+ return 0;
+}
+
+static int kill_lualc_rawwrite(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1);
+ lua_pushstring(ls, "cr_skill");
+ lua_gettable(ls, LUA_REGISTRYINDEX);
+ if (!lua_islightuserdata(ls, -1))
+ {
+ lua_settop(ls, -2);
+ fprintf(stderr, "Can't find kill string?\n");
+ return 0;
+ }
+
+ std::string *skill = static_cast<std::string *>( lua_touserdata(ls, -1) );
+ // Pop the userdata off the stack.
+ lua_settop(ls, -2);
+
+ *skill += s;
+ *skill += "\n";
+
+ return 0;
+}
+
+static int kill_lualc_write(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, 1) );
+ if (ke)
+ {
+ lua_pushstring(ls, "cr_skill");
+ lua_gettable(ls, LUA_REGISTRYINDEX);
+ if (!lua_islightuserdata(ls, -1))
+ {
+ lua_settop(ls,-2);
+ fprintf(stderr, "Can't find kill string?\n");
+ return 0;
+ }
+
+ std::string *skill = static_cast<std::string *>(
+ lua_touserdata(ls, -1) );
+ // Pop the userdata off the stack.
+ lua_settop(ls, -2);
+
+ // Write kill description and a newline.
+ *skill += ke->desc + "\n";
+ }
+ return 0;
+}
+
+static int kill_lualc_summary(lua_State *ls)
+{
+ if (!lua_istable(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type, wanted table");
+ return 0;
+ }
+
+ unsigned long count = 0;
+ for (int i = 1; ; ++i)
+ {
+ lua_rawgeti(ls, 1, i);
+ if (lua_isnil(ls, -1))
+ {
+ lua_settop(ls, -2);
+ break;
+ }
+
+ if (!lua_islightuserdata(ls, -1))
+ {
+ luaL_argerror(ls, 1, "Unexpected argument type");
+ return 0;
+ }
+
+ kill_exp *ke = static_cast<kill_exp*>( lua_touserdata(ls, -1) );
+ lua_settop(ls, -2);
+ if (ke)
+ count += ke->nkills;
+ }
+ char buf[120];
+ *buf = 0;
+ if (count)
+ snprintf(buf, sizeof buf, "%lu creature%s vanquished.",
+ count, count > 1? "s" : "");
+ lua_pushstring(ls, buf);
+ return 1;
+}
+
+static const struct luaL_reg kill_lib[] =
+{
+ { "nkills", kill_lualc_nkills },
+ { "exp" , kill_lualc_exp },
+ { "base_name", kill_lualc_base_name },
+ { "desc", kill_lualc_desc },
+ { "monnum", kill_lualc_monnum },
+ { "modifier", kill_lualc_modifier },
+ { "places", kill_lualc_places },
+ { "place_name", kill_lualc_place_name },
+ { "holiness", kill_lualc_holiness },
+ { "symbol", kill_lualc_symbol },
+ { "isghost", kill_lualc_isghost },
+ { "ispandemon", kill_lualc_ispandemon },
+ { "isunique", kill_lualc_isunique },
+ { "rawwrite", kill_lualc_rawwrite },
+ { "write", kill_lualc_write },
+ { "summary", kill_lualc_summary },
+ { NULL, NULL }
+};
+
+void lua_open_kills(lua_State *ls)
+{
+ luaL_openlib(ls, "kills", kill_lib, 0);
+}
+
+static void kill_lua_filltable(std::vector<kill_exp> &v)
+{
+ lua_State *ls = clua.state();
+ lua_newtable(ls);
+ for (int i = 0, count = v.size(); i < count; ++i)
+ {
+ lua_pushlightuserdata(ls, &v[i]);
+ lua_rawseti(ls, -2, i + 1);
+ }
+}
+
+#endif
diff --git a/trunk/source/Kills.h b/trunk/source/Kills.h
new file mode 100644
index 0000000000..28c30a88c8
--- /dev/null
+++ b/trunk/source/Kills.h
@@ -0,0 +1,186 @@
+/*
+ * File: Kills.h
+ * Summary: Tracks monsters the player has killed.
+ * Written by: Darshan Shaligram
+ */
+#ifndef KILLS_H
+#define KILLS_H
+
+#include <vector>
+#include <string>
+#include <map>
+#include <stdio.h>
+#include "enum.h"
+
+struct monsters;
+
+// Not intended for external use!
+struct kill_monster_desc
+{
+ kill_monster_desc(const struct monsters *);
+ kill_monster_desc() { }
+
+ void save(FILE*) const;
+ void load(FILE*);
+
+ enum name_modifier
+ {
+ M_NORMAL, M_ZOMBIE, M_SKELETON, M_SIMULACRUM, M_SPECTRE,
+ M_SHAPESHIFTER // A shapeshifter pretending to be 'monnum'
+ };
+
+ int monnum; // Number of the beast
+ name_modifier modifier; // Nature of the beast
+
+ struct less_than
+ {
+ bool operator () ( const kill_monster_desc &m1,
+ const kill_monster_desc &m2) const
+ {
+ return m1.monnum < m2.monnum ||
+ (m1.monnum == m2.monnum && m1.modifier < m2.modifier);
+ }
+ };
+};
+
+#define PLACE_LIMIT 5 // How many unique kill places we're prepared to track
+class kill_def
+{
+public:
+ kill_def(const struct monsters *mon);
+ kill_def() : kills(0), exp(0)
+ {
+ // This object just says to the world that it's uninitialized
+ }
+
+ void save(FILE*) const;
+ void load(FILE*);
+
+ void add_kill(const struct monsters *mon, unsigned short place);
+ void add_place(unsigned short place, bool force = false);
+
+ void merge(const kill_def &k, bool unique_monster);
+
+ std::string info(const kill_monster_desc &md) const;
+ std::string base_name(const kill_monster_desc &md) const;
+
+ unsigned short kills; // How many kills does the player have?
+ int exp; // Experience gained for slaying the beast.
+ // Only set *once*, even for shapeshifters.
+
+ std::vector<unsigned short> places; // Places where we've killed the beast.
+private:
+ std::string append_places(const kill_monster_desc &md,
+ const std::string &name) const;
+};
+
+// Ghosts and random Pandemonium demons.
+class kill_ghost
+{
+public:
+ kill_ghost(const struct monsters *mon);
+ kill_ghost() { }
+
+ void save(FILE*) const;
+ void load(FILE*);
+
+ std::string info() const;
+
+ std::string ghost_name;
+ int exp;
+ unsigned short place;
+};
+
+// This is the structure that Lua sees.
+struct kill_exp
+{
+ int nkills;
+ int exp;
+ std::string base_name;
+ std::string desc;
+
+ int monnum; // Number of the beast
+ int modifier; // Nature of the beast
+
+ std::vector<unsigned short> places;
+
+ kill_exp(const kill_def &k, const kill_monster_desc &md)
+ : nkills(k.kills), exp(k.exp), base_name(k.base_name(md)),
+ desc(k.info(md)),
+ monnum(md.monnum), modifier(md.modifier)
+ {
+ places = k.places;
+ }
+
+ kill_exp(const kill_ghost &kg)
+ : nkills(1), exp(kg.exp), base_name(), desc(kg.info()),
+ monnum(-1), modifier(0)
+ {
+ places.push_back(kg.place);
+ }
+
+ // operator< is implemented for a descending sort.
+ bool operator < ( const kill_exp &b) const
+ {
+ return exp == b.exp? (base_name < b.base_name) : (exp > b.exp);
+ }
+};
+
+class Kills
+{
+public:
+ void record_kill(const monsters *mon);
+ void merge(const Kills &k);
+
+ bool empty() const;
+ void save(FILE*) const;
+ void load(FILE*);
+
+ long get_kills(std::vector<kill_exp> &v) const;
+private:
+ typedef std::map<kill_monster_desc,
+ kill_def,
+ kill_monster_desc::less_than> kill_map;
+ typedef std::vector<kill_ghost> ghost_vec;
+
+ kill_map kills;
+ ghost_vec ghosts;
+
+ void record_ghost_kill(const struct monsters *mon);
+};
+
+class KillMaster
+{
+public:
+ void record_kill(const monsters *mon, int killer, bool ispet);
+
+ bool empty() const;
+ void save(FILE*) const;
+ void load(FILE*);
+
+ std::string kill_info() const;
+private:
+ const char *category_name(KillCategory kc) const;
+
+ Kills categorized_kills[KC_NCATEGORIES];
+private:
+ void add_kill_info(std::string &, std::vector<kill_exp> &,
+ long count, const char *c, bool separator)
+ const;
+};
+
+unsigned short get_packed_place();
+
+unsigned short get_packed_place( unsigned char branch, int subdepth,
+ char level_type );
+
+std::string short_place_name(unsigned short place);
+
+enum KILL_DUMP_OPTIONS
+{
+ KDO_NO_PLACES, // Don't dump places at all
+ KDO_ONE_PLACE, // Show places only for single kills and uniques.
+ KDO_ALL_PLACES // Show all available place information
+};
+
+#endif
diff --git a/trunk/source/abl-show.cc b/trunk/source/abl-show.cc
index 43ddae5ee1..64a1c10e74 100644
--- a/trunk/source/abl-show.cc
+++ b/trunk/source/abl-show.cc
@@ -329,6 +329,19 @@ const std::string make_cost_description( const struct ability_def &abil )
return (ret);
}
+std::vector<const char *> get_ability_names()
+{
+ std::vector<const char *> abils;
+ if (generate_abilities())
+ {
+ for (int i = 0; i < 52; ++i)
+ {
+ if (Curr_abil[i].which != ABIL_NON_ABILITY)
+ abils.push_back( get_ability_name_by_index(i) );
+ }
+ }
+ return (abils);
+}
/*
Activates a menu which gives player access to all of their non-spell
diff --git a/trunk/source/abl-show.h b/trunk/source/abl-show.h
index 52a74d167d..e901335fa1 100644
--- a/trunk/source/abl-show.h
+++ b/trunk/source/abl-show.h
@@ -15,6 +15,7 @@
#define ABLSHOW_H
#include <string>
+#include <vector>
// Structure for representing an ability:
struct ability_def
@@ -42,6 +43,8 @@ bool activate_ability( void ); // handles all special abilities now
char show_abilities( void );
bool generate_abilities( void );
+std::vector<const char *> get_ability_names( void );
+
void set_god_ability_slots( void );
diff --git a/trunk/source/acr.cc b/trunk/source/acr.cc
index 0a6af3991e..d736658d9e 100644
--- a/trunk/source/acr.cc
+++ b/trunk/source/acr.cc
@@ -55,6 +55,7 @@
#include <stdio.h>
#ifdef DOS
+#include <dos.h>
#include <conio.h>
#include <file.h>
#endif
@@ -78,6 +79,7 @@
#include "abl-show.h"
#include "abyss.h"
#include "chardump.h"
+#include "clua.h"
#include "command.h"
#include "debug.h"
#include "delay.h"
@@ -122,8 +124,10 @@
#include "stuff.h"
#include "tags.h"
#include "transfor.h"
+#include "travel.h"
#include "view.h"
#include "wpn-misc.h"
+#include "stash.h"
struct crawl_environment env;
struct player you;
@@ -383,7 +387,8 @@ static void handle_wizard_command( void )
mpr( "If you continue, your game will not be scored!", MSGCH_WARN );
#endif
- if (!yesno( "Do you really want to enter wizard mode?", false ))
+ if (!yesno( "Do you really want to enter wizard mode?",
+ false, 'n' ))
return;
you.wizard = true;
@@ -428,7 +433,7 @@ static void handle_wizard_command( void )
break;
case 'a':
- acquirement( OBJ_RANDOM );
+ acquirement( OBJ_RANDOM, AQ_WIZMODE );
break;
case 'v':
@@ -519,7 +524,7 @@ static void handle_wizard_command( void )
else
{
grd[you.x_pos][you.y_pos] = DNGN_EXIT_ABYSS;
- down_stairs(true, you.your_level);
+ down_stairs(true, you.your_level, true);
untag_followers();
}
break;
@@ -878,14 +883,29 @@ static void input(void)
}
else
{
+#ifdef STASH_TRACKING
+ if (Options.stash_tracking)
+ stashes.update_visible_stashes(
+ Options.stash_tracking == STM_ALL?
+ StashTracker::ST_AGGRESSIVE :
+ StashTracker::ST_PASSIVE);
+#endif
handle_delay();
gotoxy(18, 9);
if (you_are_delayed())
keyin = '.';
+ else if (you.activity)
+ {
+ keyin = 128;
+ you.turn_is_over = 0;
+ perform_activity();
+ }
else
{
+ if (you.running < 0) // Travel and explore
+ travel(&keyin, &move_x, &move_y);
if (you.running > 0)
{
@@ -896,7 +916,7 @@ static void input(void)
if (kbhit())
{
- you.running = 0;
+ stop_running();
goto gutch;
}
@@ -906,7 +926,7 @@ static void input(void)
keyin = '.';
}
}
- else
+ else if (!you.running)
{
#if DEBUG_DIAGNOSTICS
@@ -994,8 +1014,12 @@ static void input(void)
get_keyin_again:
#endif //jmf: just stops an annoying gcc warning
-
-
+ if (is_userfunction(keyin))
+ {
+ run_macro(get_userfunction(keyin));
+ keyin = 128;
+ }
+
switch (keyin)
{
case CONTROL('Y'):
@@ -1106,6 +1130,15 @@ static void input(void)
mpr(info);
break;
+ case CONTROL('C'):
+ case CMD_CLEAR_MAP:
+ if (you.level_type != LEVEL_LABYRINTH && you.level_type != LEVEL_ABYSS)
+ {
+ mpr("Clearing level map.");
+ clear_map();
+ }
+ break;
+
case '<':
case CMD_GO_UPSTAIRS:
if (grd[you.x_pos][you.y_pos] == DNGN_ENTER_SHOP)
@@ -1165,8 +1198,31 @@ static void input(void)
case 'd':
case CMD_DROP:
drop();
+#ifdef STASH_TRACKING
+ if (Options.stash_tracking >= STM_DROPPED)
+ stashes.add_stash();
+#endif
+ break;
+
+#ifdef STASH_TRACKING
+ case CONTROL('F'):
+ case CMD_SEARCH_STASHES:
+ stashes.search_stashes();
break;
+ case CONTROL('S'):
+ case CMD_MARK_STASH:
+ if (Options.stash_tracking >= STM_EXPLICIT)
+ stashes.add_stash(-1, -1, true);
+ break;
+
+ case CONTROL('E'):
+ case CMD_FORGET_STASH:
+ if (Options.stash_tracking >= STM_EXPLICIT)
+ stashes.no_stash();
+ break;
+#endif
+
case 'D':
case CMD_BUTCHER:
butchery();
@@ -1314,7 +1370,10 @@ static void input(void)
mpr("Press '?' for a monster description.", MSGCH_PROMPT);
struct dist lmove;
+ lmove.isValid = lmove.isTarget = lmove.isCancel = false;
look_around( lmove, true );
+ if (lmove.isValid && lmove.isTarget && !lmove.isCancel)
+ start_travel( lmove.tx, lmove.ty );
break;
case 's':
@@ -1342,6 +1401,38 @@ static void input(void)
wield_weapon(true);
break;
+ // [ds] Waypoints can be added from the level-map, and we need Ctrl+F for
+ // nobler things. Who uses waypoints, anyway?
+ // Update: Appears people do use waypoints. Reinstating, on CONTROL('W').
+ // This means Ctrl+W is no longer a wizmode trigger, but there's
+ // always '&'. :-)
+ case CMD_FIX_WAYPOINT:
+ case CONTROL('W'):
+ travel_cache.add_waypoint();
+ break;
+
+ case CMD_INTERLEVEL_TRAVEL:
+ case CONTROL('G'):
+ if (!can_travel_interlevel())
+ {
+ mpr("Sorry, you can't auto-travel out of here.");
+ break;
+ }
+ start_translevel_travel();
+ redraw_screen();
+ break;
+
+ case CONTROL('O'):
+ case CMD_EXPLORE:
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
+ {
+ mpr("It would help if you knew where you were, first.");
+ break;
+ }
+ // Start exploring
+ start_explore();
+ break;
+
case 'X':
case CMD_DISPLAY_MAP:
#if (!DEBUG_DIAGNOSTICS)
@@ -1354,6 +1445,8 @@ static void input(void)
plox[0] = 0;
show_map(plox);
redraw_screen();
+ if (plox[0] > 0)
+ start_travel(plox[0], plox[1]);
break;
case '\\':
@@ -1500,7 +1593,6 @@ static void input(void)
return;
#ifdef WIZARD
- case CONTROL('W'):
case CMD_WIZARD:
case '&':
handle_wizard_command();
@@ -1509,7 +1601,7 @@ static void input(void)
case 'S':
case CMD_SAVE_GAME:
- if (yesno("Save game and exit?", false))
+ if (yesno("Save game and exit?", false, 'n'))
save_game(true);
break;
@@ -2258,7 +2350,7 @@ static void input(void)
if (you.hp >= you.hp_max - 1
&& you.running && you.run_x == 0 && you.run_y == 0)
{
- you.running = 0;
+ stop_running();
}
inc_hp(1, false);
@@ -2280,7 +2372,7 @@ static void input(void)
if (you.magic_points >= you.max_magic_points - 1
&& you.running && you.run_x == 0 && you.run_y == 0)
{
- you.running = 0;
+ stop_running();
}
inc_mp(1, false);
@@ -2320,6 +2412,8 @@ static void input(void)
// basics for now. -- bwr
if (Visible_Statue[ STATUE_SILVER ])
{
+ interrupt_activity( AI_STATUE );
+
if ((!you.invis && one_chance_in(3)) || one_chance_in(5))
{
char wc[30];
@@ -2340,6 +2434,8 @@ static void input(void)
if (Visible_Statue[ STATUE_ORANGE_CRYSTAL ])
{
+ interrupt_activity( AI_STATUE );
+
if ((!you.invis && coinflip()) || one_chance_in(4))
{
mpr("A hostile presence attacks your mind!", MSGCH_WARN);
@@ -2602,9 +2698,7 @@ static bool initialise(void)
init_emx();
#endif
- srandom(time(NULL));
- srand(time(NULL));
- cf_setseed(); // required for stuff::coinflip()
+ seed_rng();
mons_init(mcolour); // this needs to be way up top {dlb}
init_playerspells(); // this needs to be way up top {dlb}
@@ -2697,11 +2791,23 @@ static bool initialise(void)
draw_border();
new_level();
+ travel_init_new_level();
+ // Mark items in inventory as of unknown origin.
+ origin_set_inventory(origin_set_unknown);
+
// set vision radius to player's current vision
setLOSRadius( you.current_vision );
viewwindow(1, false); // This just puts the view up for the first turn.
item();
+#ifdef CLUA_BINDINGS
+ clua.runhook("chk_startgame", "%b", ret);
+ std::string yname = you.your_name;
+ read_init_file(true);
+ strncpy(you.your_name, yname.c_str(), kNameLen);
+ you.your_name[kNameLen - 1] = 0;
+#endif
+
return (ret);
}
@@ -2819,7 +2925,7 @@ static void move_player(char move_x, char move_y)
if (you.running > 0 && you.running != 2 && check_stop_running())
{
- you.running = 0;
+ stop_running();
move_x = 0;
move_y = 0;
you.turn_is_over = 0;
@@ -2887,7 +2993,7 @@ static void move_player(char move_x, char move_y)
}
else if (targ_grid != DNGN_SHALLOW_WATER)
{
- bool enter = yesno("Do you really want to step there?", false);
+ bool enter = yesno("Do you really want to step there?", false, 'n');
if (enter)
{
@@ -2995,11 +3101,11 @@ static void move_player(char move_x, char move_y)
out_of_traps:
// BCR - Easy doors single move
- if (targ_grid == DNGN_CLOSED_DOOR && Options.easy_open)
+ if (targ_grid == DNGN_CLOSED_DOOR && (Options.easy_open || you.running < 0))
open_door(move_x, move_y);
else if (targ_grid <= MINMOVE)
{
- you.running = 0;
+ stop_running();
move_x = 0;
move_y = 0;
you.turn_is_over = 0;
diff --git a/trunk/source/beam.cc b/trunk/source/beam.cc
index 7de8d15ff1..afcbd0445b 100644
--- a/trunk/source/beam.cc
+++ b/trunk/source/beam.cc
@@ -4008,7 +4008,7 @@ void explosion( struct bolt &beam, bool hole_in_the_middle )
// turn buffering off
#ifdef WIN32CONSOLE
- bool oldValue;
+ bool oldValue = true;
if (!beam.isTracer)
oldValue = setBuffering(false);
#endif
diff --git a/trunk/source/chardump.cc b/trunk/source/chardump.cc
index 7131da279a..5fd7b18f57 100644
--- a/trunk/source/chardump.cc
+++ b/trunk/source/chardump.cc
@@ -48,16 +48,22 @@
#include "items.h"
#include "macro.h"
#include "mutation.h"
+#include "output.h"
#include "player.h"
+#include "randart.h"
#include "religion.h"
#include "shopping.h"
#include "skills2.h"
#include "spl-book.h"
#include "spl-cast.h"
#include "spl-util.h"
+#include "stash.h"
#include "stuff.h"
#include "version.h"
+#include "view.h"
+// Defined in view.cc
+extern unsigned char (*mapch2) (unsigned char);
// ========================================================================
// Internal Functions
@@ -86,7 +92,7 @@ static std::string fillstring(size_t strlen, char filler)
// macro, which is of uncertain length (well, that and I didn't know how
// to do it any better at the time) (LH)
//---------------------------------------------------------------
-static std::string munge_description(const std::string & inStr)
+std::string munge_description(const std::string & inStr)
{
std::string outStr;
@@ -159,6 +165,138 @@ static std::string munge_description(const std::string & inStr)
//---------------------------------------------------------------
//
+ // dump_screenshot
+ //
+ // Grabs a screenshot and appends the text into the given std::string,
+ // using several ugly hacks in the process.
+ //---------------------------------------------------------------
+static void dump_screenshot( std::string &text )
+{
+ // A little message history:
+ if (Options.dump_message_count > 0)
+ {
+ text += " Last Messages" EOL EOL;
+ text += get_last_messages(Options.dump_message_count);
+ }
+
+ FixedVector < char, 1500 > buffy; //[800]; //392];
+ int bufcount = 0;
+ unsigned short ch, color;
+ int count_x, count_y;
+
+ // Urg, ugly screen capture. CVS Crawl may have a better way of doing this,
+ // but until the next release...
+ for (count_y = (you.y_pos - 8); (count_y < you.y_pos + 9); count_y++)
+ {
+ bufcount += 8;
+ for (count_x = (you.x_pos - 8); (count_x < you.x_pos + 9); count_x++)
+ {
+ if (count_x == you.x_pos && count_y == you.y_pos)
+ {
+ extern unsigned char your_sign;
+ ch = your_sign;
+ }
+ else
+ {
+ unsigned int object = env.show[count_x - you.x_pos + 9]
+ [count_y - you.y_pos + 9];
+ get_non_ibm_symbol(object, &ch, &color);
+ }
+
+ buffy[bufcount++] = (char) ch;
+ }
+ bufcount += 8;
+ }
+
+ int maxbuf = bufcount;
+ bufcount = 0;
+
+ for (count_y = 0; count_y < 17; count_y++)
+ {
+ for (count_x = 0; count_x < 33; count_x++)
+ {
+ if (count_x + you.x_pos - 17 < 3
+ || count_y + you.y_pos - 9 < 3
+ || count_x + you.x_pos - 14 > (GXM - 3)
+ || count_y + you.y_pos - 9 > (GYM - 3))
+ {
+ buffy[bufcount++] = ' ';
+ continue;
+ }
+
+ if (count_x >= 8 && count_x <= 24 && count_y >= 0
+ && count_y <= 16 && buffy[bufcount] != 0)
+ {
+ bufcount++;
+ continue;
+ }
+
+ unsigned char envc = (unsigned char)
+ env.map[count_x + you.x_pos - 17]
+ [count_y + you.y_pos - 9];
+ if (envc)
+ {
+ // If it's printable, use it directly.
+ if (envc < 127 && envc >= 32)
+ ch = envc;
+ else
+ {
+ // Otherwise get what's on the grid and get an ASCII
+ // character for that.
+ unsigned int object = grd[count_x + you.x_pos - 16]
+ [count_y + you.y_pos - 8];
+
+ // Special case secret doors so that monsters that open
+ // doors out of hero's LOS don't reveal the secret door in
+ // the dump
+ if (envc == mapch2(DNGN_SECRET_DOOR))
+ object = DNGN_SECRET_DOOR;
+
+ get_non_ibm_symbol(object, &ch, &color);
+ }
+
+ buffy[bufcount++] = (char) ch;
+ }
+ else
+ {
+ buffy[bufcount++] = ' ';
+ }
+ }
+ }
+
+ if (bufcount > maxbuf) maxbuf = bufcount;
+
+ while (maxbuf > 0 && (!buffy[maxbuf - 1] || buffy[maxbuf - 1] == ' '))
+ --maxbuf;
+
+ // 33 columns and a null terminator. More hardcoding. :-(
+ char buf[34];
+ char *s = buf;
+ bool leadblanks = true;
+ for (int i = 0; i < maxbuf; )
+ {
+ *s++ = buffy[i]? buffy[i] : ' ';
+
+ ++i;
+ if (!(i % 33) || i >= maxbuf)
+ {
+ *s = 0;
+ while (s > buf && *--s == ' ')
+ *s = 0;
+
+ if (s == buf && !*s && leadblanks)
+ continue;
+
+ leadblanks = false;
+ text += buf;
+ text += EOL;
+ s = buf;
+ }
+ }
+}
+
+ //---------------------------------------------------------------
+ //
// dump_stats
//
//---------------------------------------------------------------
@@ -298,6 +436,47 @@ static void dump_stats( std::string & text )
//---------------------------------------------------------------
//
+ // dump_stats2
+ //
+ //---------------------------------------------------------------
+static void dump_stats2( std::string & text, bool calc_unid)
+{
+ char buffer[25*3][45];
+ char str_pass[80];
+ char* ptr_n;
+
+ get_full_detail(&buffer[0][0], calc_unid);
+
+ for (int i = 0; i < 25; i++)
+ {
+ ptr_n = &buffer[i][0];
+ if (buffer[i+25][0] == '\0' && buffer[i+50][0] == '\0')
+ snprintf(&str_pass[0], 45, "%s", ptr_n);
+ else
+ snprintf(&str_pass[0], 45, "%-32s", ptr_n);
+ text += str_pass;
+
+ ptr_n = &buffer[i+25][0];
+ if (buffer[i+50][0] == '\0')
+ snprintf(&str_pass[0], 45, "%s", ptr_n);
+ else
+ snprintf(&str_pass[0], 45, "%-20s", ptr_n);
+ text += str_pass;
+
+ ptr_n = &buffer[i+50][0];
+ if (buffer[i+50][0] != '\0')
+ {
+ snprintf(&str_pass[0], 45, "%s", ptr_n);
+ text += str_pass;
+ }
+ text += EOL;
+ }
+
+ text += EOL EOL;
+}
+
+ //---------------------------------------------------------------
+ //
// dump_location
//
//---------------------------------------------------------------
@@ -410,6 +589,57 @@ static void dump_religion( std::string & text )
}
} // end dump_religion()
+extern char id[4][50]; // itemname.cc
+static bool dump_item_origin(const item_def &item, int value)
+{
+#define fs(x) (flags & (x))
+ const int flags = Options.dump_item_origins;
+ if (flags == IODS_EVERYTHING)
+ return (true);
+
+ if (fs(IODS_ARTIFACTS)
+ && (is_random_artefact(item) || is_fixed_artefact(item))
+ && item_ident(item, ISFLAG_KNOW_PROPERTIES))
+ return (true);
+
+ if (fs(IODS_EGO_ARMOUR) && item.base_type == OBJ_ARMOUR
+ && item_ident( item, ISFLAG_KNOW_TYPE ))
+ {
+ const int spec_ench = get_armour_ego_type( item );
+ return (spec_ench != SPARM_NORMAL);
+ }
+
+ if (fs(IODS_EGO_WEAPON) && item.base_type == OBJ_WEAPONS
+ && item_ident( item, ISFLAG_KNOW_TYPE ))
+ return (get_weapon_brand(item) != SPWPN_NORMAL);
+
+ if (fs(IODS_JEWELLERY) && item.base_type == OBJ_JEWELLERY)
+ return (true);
+
+ if (fs(IODS_RUNES) && item.base_type == OBJ_MISCELLANY
+ && item.sub_type == MISC_RUNE_OF_ZOT)
+ return (true);
+
+ if (fs(IODS_RODS) && item.base_type == OBJ_STAVES
+ && item_is_rod(item))
+ return (true);
+
+ if (fs(IODS_STAVES) && item.base_type == OBJ_STAVES
+ && !item_is_rod(item))
+ return (true);
+
+ if (fs(IODS_BOOKS) && item.base_type == OBJ_BOOKS)
+ return (true);
+
+ const int refpr = Options.dump_item_origin_price;
+ if (refpr == -1)
+ return (false);
+ if (value == -1)
+ value = item_value( item, id, false );
+ return (value >= refpr);
+#undef fs
+}
+
//---------------------------------------------------------------
//
// dump_inventory
@@ -496,17 +726,25 @@ static void dump_inventory( std::string & text, bool show_prices )
inv_count--;
+ int ival = -1;
if (show_prices)
{
text += " (";
- itoa( item_value( you.inv[j], temp_id, true ),
+ itoa( ival =
+ item_value( you.inv[j], temp_id, true ),
tmp_quant, 10 );
text += tmp_quant;
text += " gold)";
}
+ if (origin_describable(you.inv[j])
+ && dump_item_origin(you.inv[j], ival))
+ {
+ text += EOL " (" + origin_desc(you.inv[j]) + ")";
+ }
+
if (is_dumpable_artifact( you.inv[j],
Options.verbose_dump ))
{
@@ -590,7 +828,8 @@ static void dump_spells( std::string & text )
// This array helps output the spell types in the traditional order.
// this can be tossed as soon as I reorder the enum to the traditional order {dlb}
- const int spell_type_index[] = {
+ const int spell_type_index[] =
+ {
SPTYP_HOLY,
SPTYP_POISON,
SPTYP_FIRE,
@@ -670,6 +909,8 @@ static void dump_spells( std::string & text )
}
}
+ if (spell_line.length() > 57)
+ spell_line = spell_line.substr(0, 57);
for (int i = spell_line.length(); i < 58; i++)
{
spell_line += ' ';
@@ -703,6 +944,17 @@ static void dump_spells( std::string & text )
}
} // end dump_spells()
+
+//---------------------------------------------------------------
+//
+// dump_kills
+//
+//---------------------------------------------------------------
+static void dump_kills( std::string & text )
+{
+ text += you.kills.kill_info();
+}
+
//---------------------------------------------------------------
//
// dump_mutations
@@ -748,6 +1000,14 @@ static void dump_mutations( std::string & text )
// Public Functions
// ========================================================================
+const char *hunger_level(void)
+{
+ return ((you.hunger <= 1000) ? "starving" :
+ (you.hunger <= 2600) ? "hungry" :
+ (you.hunger < 7000) ? "not hungry" :
+ (you.hunger < 11000) ? "full" : "completely stuffed");
+}
+
//---------------------------------------------------------------
//
// dump_char
@@ -769,7 +1029,11 @@ bool dump_char( const char fname[30], bool show_prices ) // $$$ a try block?
text += EOL;
text += EOL;
- dump_stats(text);
+ if (Options.detailed_stat_dump)
+ dump_stats2(text, show_prices);
+ else
+ dump_stats(text);
+
dump_location(text);
dump_religion(text);
@@ -787,10 +1051,7 @@ bool dump_char( const char fname[30], bool show_prices ) // $$$ a try block?
text += "You are ";
- text += ((you.hunger <= 1000) ? "starving" :
- (you.hunger <= 2600) ? "hungry" :
- (you.hunger < 7000) ? "not hungry" :
- (you.hunger < 11000) ? "full" : "completely stuffed");
+ text += hunger_level();
text += ".";
text += EOL;
@@ -845,6 +1106,14 @@ bool dump_char( const char fname[30], bool show_prices ) // $$$ a try block?
dump_spells(text);
dump_mutations(text);
+ text += EOL;
+ text += EOL;
+
+ dump_screenshot(text);
+ text += EOL EOL;
+
+ dump_kills(text);
+
char file_name[kPathLen] = "\0";
if (SysEnv.crawl_dir)
@@ -852,8 +1121,29 @@ bool dump_char( const char fname[30], bool show_prices ) // $$$ a try block?
strncat(file_name, fname, kPathLen);
+#ifdef STASH_TRACKING
+ char stash_file_name[kPathLen] = "";
+ strncpy(stash_file_name, file_name, kPathLen);
+#endif
if (strcmp(fname, "morgue.txt") != 0)
+ {
strncat(file_name, ".txt", kPathLen);
+#ifdef STASH_TRACKING
+ strncat(stash_file_name, ".lst", kPathLen);
+ stashes.dump(stash_file_name);
+#endif
+ }
+#ifdef STASH_TRACKING
+ else
+ {
+ // Grr. Filename is morgue.txt, it needs to be morgue.lst
+ int len = strlen(stash_file_name);
+ stash_file_name[len - 3] = 'l';
+ stash_file_name[len - 2] = 's';
+ // Fully identified stash dump.
+ stashes.dump(stash_file_name, true);
+ }
+#endif
FILE *handle = fopen(file_name, "wb");
@@ -874,9 +1164,6 @@ bool dump_char( const char fname[30], bool show_prices ) // $$$ a try block?
size_t len = end - begin;
- if (len > 80)
- len = 80;
-
fwrite(text.c_str() + begin, len, 1, handle);
begin = end;
diff --git a/trunk/source/chardump.h b/trunk/source/chardump.h
index 2609d61615..22b03ac8d3 100644
--- a/trunk/source/chardump.h
+++ b/trunk/source/chardump.h
@@ -13,6 +13,7 @@
#ifndef CHARDUMP_H
#define CHARDUMP_H
+#include <string>
// last updated 12may2000 {dlb}
/* ***********************************************************************
@@ -20,5 +21,8 @@
* *********************************************************************** */
bool dump_char( const char fname[30], bool show_prices );
+std::string munge_description(const std::string &inStr);
+
+const char *hunger_level(void);
#endif
diff --git a/trunk/source/clua.cc b/trunk/source/clua.cc
new file mode 100644
index 0000000000..21f2566c15
--- /dev/null
+++ b/trunk/source/clua.cc
@@ -0,0 +1,2330 @@
+#include "AppHdr.h"
+
+#ifdef CLUA_BINDINGS
+
+#include "clua.h"
+
+#include "abl-show.h"
+#include "command.h"
+#include "chardump.h"
+#include "food.h"
+#include "invent.h"
+#include "initfile.h"
+#include "itemname.h"
+#include "items.h"
+#include "item_use.h"
+#include "libutil.h"
+#include "macro.h"
+#include "message.h"
+#include "mon-util.h"
+#include "output.h"
+#include "player.h"
+#include "randart.h"
+#include "skills2.h"
+#include "spl-util.h"
+#include "stuff.h"
+#include "wpn-misc.h"
+
+#include <cstring>
+
+#ifdef HASH_CONTAINERS
+# include <hash_map>
+# define CHMAP HASH_CONTAINER_NS::hash_map
+#else
+# include <map>
+# define CHMAP std::map
+#endif
+
+#include <cctype>
+
+#define CL_RESETSTACK_RETURN(ls, oldtop, retval) \
+ if (true) {\
+ if (oldtop != lua_gettop(ls)) { \
+ lua_settop(ls, oldtop); \
+ } \
+ return (retval); \
+ } \
+ else
+
+CLua clua;
+
+CLua::CLua() : _state(NULL), sourced_files(), uniqindex(0L)
+{
+}
+
+CLua::~CLua()
+{
+ if (_state)
+ lua_close(_state);
+}
+
+// This has the disadvantage of repeatedly trying init_lua if it fails.
+lua_State *CLua::state()
+{
+ if (!_state)
+ init_lua();
+ return _state;
+}
+
+void CLua::setglobal(const char *name)
+{
+ lua_setglobal(state(), name);
+}
+
+void CLua::getglobal(const char *name)
+{
+ lua_getglobal(state(), name);
+}
+
+std::string CLua::setuniqregistry()
+{
+ char name[100];
+ snprintf(name, sizeof name, "__cru%lu", uniqindex++);
+ lua_pushstring(state(), name);
+ lua_insert(state(), -2);
+ lua_settable(state(), LUA_REGISTRYINDEX);
+
+ return (name);
+}
+
+void CLua::setregistry(const char *name)
+{
+ lua_pushstring(state(), name);
+ // Slide name round before the value
+ lua_insert(state(), -2);
+ lua_settable(state(), LUA_REGISTRYINDEX);
+}
+
+void CLua::getregistry(const char *name)
+{
+ lua_pushstring(state(), name);
+ lua_gettable(state(), LUA_REGISTRYINDEX);
+}
+
+void CLua::save(const char *file)
+{
+ if (!_state)
+ return;
+
+ CLuaSave clsave = { file, NULL };
+ callfn("c_save", "u", &clsave);
+ if (clsave.handle)
+ fclose(clsave.handle);
+}
+
+int CLua::file_write(lua_State *ls)
+{
+ if (!lua_islightuserdata(ls, 1))
+ {
+ luaL_argerror(ls, 1, "Expected filehandle at arg 1");
+ return (0);
+ }
+ CLuaSave *sf = static_cast<CLuaSave *>( lua_touserdata(ls, 1) );
+ if (!sf)
+ return (0);
+
+ FILE *f = sf->get_file();
+ if (!f)
+ return (0);
+
+ const char *text = luaL_checkstring(ls, 2);
+ if (text)
+ fprintf(f, "%s", text);
+ return (0);
+}
+
+FILE *CLua::CLuaSave::get_file()
+{
+ if (!handle)
+ handle = fopen(filename, "w");
+
+ return (handle);
+}
+
+void CLua::set_error(int err, lua_State *ls)
+{
+ if (!err)
+ {
+ error.clear();
+ return;
+ }
+ if (!ls && !(ls = _state))
+ {
+ error = "<LUA not initialized>";
+ return;
+ }
+ const char *serr = lua_tostring(ls, -1);
+ lua_pop(ls, 1);
+ error = serr? serr : "<Unknown error>";
+}
+
+int CLua::execstring(const char *s, const char *context)
+{
+ lua_State *ls = state();
+ int err = luaL_loadbuffer(ls, s, strlen(s), context);
+ if (err)
+ {
+ set_error(err, ls);
+ return err;
+ }
+ err = lua_pcall(ls, 0, 0, 0);
+ set_error(err, ls);
+ return err;
+}
+
+int CLua::execfile(const char *filename)
+{
+ if (sourced_files.find(filename) != sourced_files.end())
+ return 0;
+
+ sourced_files.insert(filename);
+
+ FILE *f = fopen(filename, "r");
+ if (f)
+ fclose(f);
+ else
+ {
+ error = std::string("Can't read ") + filename;
+ return -1;
+ }
+
+ lua_State *ls = state();
+ if (!ls)
+ return -1;
+
+ int err = luaL_loadfile(ls, filename);
+ if (!err)
+ err = lua_pcall(ls, 0, 0, 0);
+ set_error(err);
+ return (err);
+}
+
+bool CLua::runhook(const char *hook, const char *params, ...)
+{
+ error.clear();
+
+ lua_State *ls = state();
+ if (!ls)
+ return (false);
+
+ // Remember top of stack, for debugging porpoises
+ int stack_top = lua_gettop(ls);
+ lua_getglobal(ls, hook);
+ if (!lua_istable(ls, -1))
+ {
+ lua_pop(ls, 1);
+ CL_RESETSTACK_RETURN( ls, stack_top, false );
+ }
+ for (int i = 1; ; ++i)
+ {
+ int currtop = lua_gettop(ls);
+ lua_rawgeti(ls, -1, i);
+ if (!lua_isfunction(ls, -1))
+ {
+ lua_pop(ls, 1);
+ break;
+ }
+
+ // So what's on top *is* a function. Call it with the args we have.
+ va_list args;
+ va_start(args, params);
+ calltopfn(ls, params, args);
+ va_end(args);
+
+ lua_settop(ls, currtop);
+ }
+ CL_RESETSTACK_RETURN( ls, stack_top, true );
+}
+
+void CLua::fnreturns(const char *format, ...)
+{
+ lua_State *ls = _state;
+
+ if (!format || !ls)
+ return;
+
+ va_list args;
+ va_start(args, format);
+
+ vfnreturns(format, args);
+
+ va_end(args);
+}
+
+void CLua::vfnreturns(const char *format, va_list args)
+{
+ lua_State *ls = _state;
+ int nrets = return_count(ls, format);
+ int sp = -nrets - 1;
+
+ const char *gs = strchr(format, '>');
+ if (gs)
+ format = gs + 1;
+ else if ((gs = strchr(format, ':')))
+ format = gs + 1;
+
+ for (const char *run = format; *run; ++run)
+ {
+ char argtype = *run;
+ ++sp;
+ switch (argtype)
+ {
+ case 'u':
+ if (lua_islightuserdata(ls, sp))
+ *(va_arg(args, void**)) = lua_touserdata(ls, sp);
+ break;
+ case 'd':
+ if (lua_isnumber(ls, sp))
+ *(va_arg(args, int*)) = luaL_checkint(ls, sp);
+ break;
+ case 'b':
+ *(va_arg(args, bool *)) = lua_toboolean(ls, sp);
+ break;
+ case 's':
+ {
+ const char *s = lua_tostring(ls, sp);
+ if (s)
+ *(va_arg(args, std::string *)) = s;
+ break;
+ }
+ default:
+ break;
+ }
+
+ }
+ // Pop args off the stack
+ lua_pop(ls, nrets);
+}
+
+static void push_monster(lua_State *ls, monsters *mons);
+static int push_activity_interrupt(lua_State *ls, activity_interrupt_t *t);
+int CLua::push_args(lua_State *ls, const char *format, va_list args,
+ va_list *targ)
+{
+ if (!format)
+ {
+ if (targ)
+ va_copy(*targ, args);
+ return (0);
+ }
+
+ const char *cs = strchr(format, ':');
+ if (cs)
+ format = cs + 1;
+
+ int argc = 0;
+ for (const char *run = format; *run; run++) {
+ if (*run == '>')
+ break;
+
+ char argtype = *run;
+ ++argc;
+ switch (argtype) {
+ case 'u': // Light userdata
+ lua_pushlightuserdata(ls, va_arg(args, void*));
+ break;
+ case 's': // String
+ {
+ const char *s = va_arg(args, const char *);
+ if (s)
+ lua_pushstring(ls, s);
+ else
+ lua_pushnil(ls);
+ break;
+ }
+ case 'd': // Integer
+ lua_pushnumber(ls, va_arg(args, int));
+ break;
+ case 'L':
+ lua_pushnumber(ls, va_arg(args, long));
+ break;
+ case 'b':
+ lua_pushboolean(ls, va_arg(args, int));
+ break;
+ case 'M':
+ push_monster(ls, va_arg(args, monsters *));
+ break;
+ case 'A':
+ argc += push_activity_interrupt(
+ ls, va_arg(args, activity_interrupt_t *));
+ break;
+ default:
+ --argc;
+ break;
+ }
+ }
+ if (targ)
+ va_copy(*targ, args);
+ return (argc);
+}
+
+int CLua::return_count(lua_State *ls, const char *format)
+{
+ if (!format)
+ return (0);
+
+ const char *gs = strchr(format, '>');
+ if (gs)
+ return (strlen(gs + 1));
+
+ const char *cs = strchr(format, ':');
+ if (cs && isdigit(*format))
+ {
+ char *es = NULL;
+ int ci = strtol(format, &es, 10);
+ // We're capping return at 10 here, which is arbitrary, but avoids
+ // blowing the stack.
+ if (ci < 0)
+ ci = 0;
+ else if (ci > 5)
+ ci = 10;
+ return (ci);
+ }
+ return (0);
+}
+
+bool CLua::calltopfn(lua_State *ls, const char *params, va_list args,
+ int retc, va_list *copyto)
+{
+ // We guarantee to remove the function from the stack
+ int argc = push_args(ls, params, args, copyto);
+ if (retc == -1)
+ retc = return_count(ls, params);
+ int err = lua_pcall(ls, argc, retc, 0);
+ set_error(err, ls);
+ return (!err);
+}
+
+bool CLua::callbooleanfn(bool def, const char *fn, const char *params, ...)
+{
+ error.clear();
+ lua_State *ls = state();
+ if (!ls)
+ return (def);
+
+ int stacktop = lua_gettop(ls);
+
+ lua_getglobal(ls, fn);
+ if (!lua_isfunction(ls, -1))
+ {
+ lua_pop(ls, 1);
+ CL_RESETSTACK_RETURN(ls, stacktop, def);
+ }
+
+ va_list args;
+ va_start(args, params);
+ bool ret = calltopfn(ls, params, args, 1);
+ if (!ret)
+ CL_RESETSTACK_RETURN(ls, stacktop, def);
+
+ def = lua_toboolean(ls, -1);
+ CL_RESETSTACK_RETURN(ls, stacktop, def);
+}
+
+bool CLua::proc_returns(const char *par) const
+{
+ return (strchr(par, '>') != NULL);
+}
+
+bool CLua::callfn(const char *fn, const char *params, ...)
+{
+ error.clear();
+ lua_State *ls = state();
+ if (!ls)
+ return (false);
+
+ lua_getglobal(ls, fn);
+ if (!lua_isfunction(ls, -1))
+ {
+ lua_pop(ls, 1);
+ return (false);
+ }
+
+ va_list args;
+ va_list fnret;
+ va_start(args, params);
+ bool ret = calltopfn(ls, params, args, -1, &fnret);
+ if (ret)
+ {
+ // If we have a > in format, gather return params now.
+ if (proc_returns(params))
+ vfnreturns(params, fnret);
+ }
+ va_end(args);
+ va_end(fnret);
+ return (ret);
+}
+
+bool CLua::callfn(const char *fn, int nargs, int nret)
+{
+ error.clear();
+ lua_State *ls = state();
+ if (!ls)
+ return (false);
+
+ lua_getglobal(ls, fn);
+ if (!lua_isfunction(ls, -1))
+ {
+ lua_settop(ls, -nargs - 2);
+ return (false);
+ }
+
+ // Slide the function in front of its args and call it.
+ if (nargs)
+ lua_insert(ls, -nargs - 1);
+ int err = lua_pcall(ls, nargs, nret, 0);
+ set_error(err, ls);
+ return !err;
+}
+
+// Defined in Kills.cc because the kill bindings refer to Kills.cc local
+// structs
+extern void lua_open_kills(lua_State *ls);
+
+void lua_open_you(lua_State *ls);
+void lua_open_item(lua_State *ls);
+void lua_open_food(lua_State *ls);
+void lua_open_crawl(lua_State *ls);
+void lua_open_file(lua_State *ls);
+void lua_open_options(lua_State *ls);
+void lua_open_monsters(lua_State *ls);
+void lua_open_globals(lua_State *ls);
+
+
+void CLua::init_lua()
+{
+ if (_state)
+ return;
+
+ _state = lua_open();
+ if (!_state)
+ return;
+ luaopen_base(_state);
+ luaopen_string(_state);
+ luaopen_table(_state);
+
+ // Open Crawl bindings
+ lua_open_kills(_state);
+ lua_open_you(_state);
+ lua_open_item(_state);
+ lua_open_food(_state);
+ lua_open_crawl(_state);
+ lua_open_file(_state);
+ lua_open_options(_state);
+ lua_open_monsters(_state);
+
+ lua_open_globals(_state);
+
+ load_cmacro();
+ load_chooks();
+}
+
+void CLua::load_chooks()
+{
+ // All hook names must be chk_????
+ static const char *c_hooks =
+ "chk_startgame = { }"
+ ;
+ execstring(c_hooks, "base");
+}
+
+void CLua::load_cmacro()
+{
+ static const char *c_macro =
+ "function c_macro(fn)"
+ " if fn == nil then"
+ " if c_macro_coroutine ~= nil then"
+ " local coret, mret"
+ " coret, mret = coroutine.resume(c_macro_coroutine)"
+ " if not coret or not mret then"
+ " c_macro_coroutine = nil"
+ " c_macro_name = nil"
+ " end"
+ " if not coret and mret then"
+ " error(mret)"
+ " end"
+ " return (coret and mret)"
+ " end"
+ " return false"
+ " end"
+ " if _G[fn] == nil or type(_G[fn]) ~= 'function' then"
+ " return false"
+ " end"
+ " c_macro_name = fn"
+ " c_macro_coroutine = coroutine.create(_G[fn]) "
+ " return c_macro() "
+ "end";
+ execstring(c_macro, "base");
+}
+
+/////////////////////////////////////////////////////////////////////
+
+#define LUAWRAP(name, wrapexpr) \
+ static int name(lua_State *ls) \
+ { \
+ wrapexpr; \
+ return (0); \
+ }
+
+#define LUARET1(name, type, val) \
+ static int name(lua_State *ls) \
+ { \
+ lua_push##type(ls, val); \
+ return (1); \
+ }
+
+#define LUARET2(name, type, val1, val2) \
+ static int name(lua_State *ls) \
+ { \
+ lua_push##type(ls, val1); \
+ lua_push##type(ls, val2); \
+ return (2); \
+ }
+
+template <class T> T *util_get_userdata(lua_State *ls, int ndx)
+{
+ return (lua_islightuserdata(ls, ndx))?
+ static_cast<T *>( lua_touserdata(ls, ndx) )
+ : NULL;
+}
+
+template <class T> T *clua_get_userdata(lua_State *ls, const char *mt)
+{
+ return static_cast<T*>( luaL_checkudata( ls, 1, mt ) );
+}
+
+static void clua_register_metatable(lua_State *ls, const char *tn,
+ const luaL_reg *lr,
+ int (*gcfn)(lua_State *ls) = NULL)
+{
+ int top = lua_gettop(ls);
+
+ luaL_newmetatable(ls, tn);
+ lua_pushstring(ls, "__index");
+ lua_pushvalue(ls, -2);
+ lua_settable(ls, -3);
+
+ if (gcfn)
+ {
+ lua_pushstring(ls, "__gc");
+ lua_pushcfunction(ls, gcfn);
+ lua_settable(ls, -3);
+ }
+
+ luaL_openlib(ls, NULL, lr, 0);
+
+ lua_settop(ls, top);
+}
+
+template <class T> T *clua_new_userdata(
+ lua_State *ls, const char *mt)
+{
+ void *udata = lua_newuserdata( ls, sizeof(T) );
+ luaL_getmetatable(ls, mt);
+ lua_setmetatable(ls, -2);
+ return static_cast<T*>( udata );
+}
+
+/////////////////////////////////////////////////////////////////////
+// Bindings to get information on the player
+//
+
+static const char *transform_name()
+{
+ switch (you.attribute[ATTR_TRANSFORMATION])
+ {
+ case TRAN_SPIDER:
+ return "spider";
+ case TRAN_BLADE_HANDS:
+ return "blade";
+ case TRAN_STATUE:
+ return "statue";
+ case TRAN_ICE_BEAST:
+ return "ice";
+ case TRAN_DRAGON:
+ return "dragon";
+ case TRAN_LICH:
+ return "lich";
+ case TRAN_SERPENT_OF_HELL:
+ return "serpent";
+ case TRAN_AIR:
+ return "air";
+ default:
+ return "";
+ }
+}
+
+LUARET1(you_turn_is_over, boolean, you.turn_is_over)
+LUARET1(you_name, string, you.your_name)
+LUARET1(you_race, string, species_name(you.species, you.experience_level))
+LUARET1(you_class, string, get_class_name(you.char_class))
+LUARET2(you_hp, number, you.hp, you.hp_max)
+LUARET2(you_mp, number, you.magic_points, you.max_magic_points)
+LUARET1(you_hunger, string, hunger_level())
+LUARET2(you_strength, number, you.strength, you.max_strength)
+LUARET2(you_intelligence, number, you.intel, you.max_intel)
+LUARET2(you_dexterity, number, you.dex, you.max_dex)
+LUARET1(you_exp, number, you.experience_level)
+LUARET1(you_exp_points, number, you.experience)
+LUARET1(you_res_poison, number, player_res_poison(false))
+LUARET1(you_res_fire, number, player_res_fire(false))
+LUARET1(you_res_cold, number, player_res_cold(false))
+LUARET1(you_res_draining, number, player_prot_life(false))
+LUARET1(you_res_shock, number, player_res_electricity(false))
+LUARET1(you_res_statdrain, number, player_sust_abil(false))
+LUARET1(you_res_mutation, number, wearing_amulet(AMU_RESIST_MUTATION, false))
+LUARET1(you_res_slowing, number, wearing_amulet(AMU_RESIST_SLOW, false))
+LUARET1(you_gourmand, boolean, wearing_amulet(AMU_THE_GOURMAND, false))
+LUARET1(you_levitating, boolean,
+ player_is_levitating() && !wearing_amulet(AMU_CONTROLLED_FLIGHT))
+LUARET1(you_flying, boolean,
+ player_is_levitating() && wearing_amulet(AMU_CONTROLLED_FLIGHT))
+LUARET1(you_transform, string, transform_name())
+LUAWRAP(you_stop_activity, interrupt_activity(AI_FORCE_INTERRUPT))
+
+void lua_push_floor_items(lua_State *ls);
+static int you_floor_items(lua_State *ls)
+{
+ lua_push_floor_items(ls);
+ return (1);
+}
+
+static int l_you_spells(lua_State *ls)
+{
+ lua_newtable(ls);
+ int index = 0;
+ for (int i = 0; i < 52; ++i)
+ {
+ const int spell = get_spell_by_letter( index_to_letter(i) );
+ if (spell == SPELL_NO_SPELL)
+ continue;
+
+ lua_pushstring(ls, spell_title(spell));
+ lua_rawseti(ls, -2, ++index);
+ }
+ return (1);
+}
+
+static int l_you_abils(lua_State *ls)
+{
+ lua_newtable(ls);
+
+ std::vector<const char *>abils = get_ability_names();
+ for (int i = 0, size = abils.size(); i < size; ++i)
+ {
+ lua_pushstring(ls, abils[i]);
+ lua_rawseti(ls, -2, i + 1);
+ }
+ return (1);
+}
+
+static const struct luaL_reg you_lib[] =
+{
+ { "turn_is_over", you_turn_is_over },
+ { "spells" , l_you_spells },
+ { "abilities" , l_you_abils },
+ { "name" , you_name },
+ { "race" , you_race },
+ { "class" , you_class },
+ { "hp" , you_hp },
+ { "mp" , you_mp },
+ { "hunger" , you_hunger },
+ { "strength" , you_strength },
+ { "intelligence", you_intelligence },
+ { "dexterity" , you_dexterity },
+ { "exp" , you_exp },
+ { "exp_points" , you_exp_points },
+
+ { "res_poison" , you_res_poison },
+ { "res_fire" , you_res_fire },
+ { "res_cold" , you_res_cold },
+ { "res_draining", you_res_draining },
+ { "res_shock" , you_res_shock },
+ { "res_statdrain", you_res_statdrain },
+ { "res_mutation", you_res_mutation },
+ { "res_slowing", you_res_slowing },
+ { "gourmand", you_gourmand },
+ { "levitating", you_levitating },
+ { "flying", you_flying },
+ { "transform", you_transform },
+
+ { "stop_activity", you_stop_activity },
+
+ { "floor_items", you_floor_items },
+ { NULL, NULL },
+};
+
+void lua_open_you(lua_State *ls)
+{
+ luaL_openlib(ls, "you", you_lib, 0);
+}
+
+/////////////////////////////////////////////////////////////////////
+// Bindings to get information on items. We must be extremely careful
+// to only hand out information the player already has.
+//
+
+static const item_def *excl_item = NULL;
+
+#define LUA_ITEM(name, n) \
+ if (!lua_islightuserdata(ls, n)) \
+ { \
+ luaL_argerror(ls, n, "Unexpected arg type"); \
+ return (0); \
+ } \
+ \
+ item_def *name = static_cast<item_def *>( lua_touserdata(ls, n ) ); \
+ if (excl_item && name != excl_item) \
+ { \
+ luaL_argerror(ls, n, "Unexpected item"); \
+ return (0); \
+ }
+
+void lua_push_inv_items(lua_State *ls);
+
+void lua_set_exclusive_item(const item_def *item)
+{
+ excl_item = item;
+}
+
+static int l_item_inventory(lua_State *ls)
+{
+ lua_push_inv_items(ls);
+ return (1);
+}
+
+static int l_item_index_to_letter(lua_State *ls)
+{
+ int index = luaL_checkint(ls, 1);
+ char sletter[2] = "?";
+ if (index >= 0 && index <= ENDOFPACK)
+ *sletter = index_to_letter(index);
+ lua_pushstring(ls, sletter);
+ return (1);
+}
+
+static int l_item_letter_to_index(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1);
+ if (!s || !*s || s[1])
+ return (0);
+ lua_pushnumber(ls, letter_to_index(*s));
+ return (1);
+}
+
+static int l_item_swap_slots(lua_State *ls)
+{
+ int slot1 = luaL_checkint(ls, 1),
+ slot2 = luaL_checkint(ls, 2);
+ bool verbose = lua_toboolean(ls, 3);
+ if (slot1 < 0 || slot1 >= ENDOFPACK ||
+ slot2 < 0 || slot2 >= ENDOFPACK ||
+ slot1 == slot2 || !is_valid_item(you.inv[slot1]))
+ return (0);
+
+ swap_inv_slots(slot1, slot2, verbose);
+
+ return (0);
+}
+
+static int l_item_wield(lua_State *ls)
+{
+ if (you.turn_is_over)
+ return (0);
+
+ LUA_ITEM(item, 1);
+ int slot = -1;
+ if (item && is_valid_item(*item) && in_inventory(*item))
+ slot = item->link;
+ bool res = wield_weapon(true, slot);
+ lua_pushboolean(ls, res);
+ return (1);
+}
+
+static int l_item_wear(lua_State *ls)
+{
+ if (you.turn_is_over)
+ return (0);
+
+ LUA_ITEM(item, 1);
+ if (!item || !in_inventory(*item))
+ return (0);
+
+ bool success = do_wear_armour(item->link, false);
+ lua_pushboolean(ls, success);
+ return (1);
+}
+
+static int l_item_puton(lua_State *ls)
+{
+ if (you.turn_is_over)
+ return (0);
+
+ LUA_ITEM(item, 1);
+ if (!item || !in_inventory(*item))
+ return (0);
+
+ lua_pushboolean(ls, puton_ring(item->link, false));
+ return (1);
+}
+
+static int l_item_remove(lua_State *ls)
+{
+ if (you.turn_is_over)
+ {
+ mpr("Turn is over");
+ return (0);
+ }
+
+ LUA_ITEM(item, 1);
+ if (!item || !in_inventory(*item))
+ {
+ mpr("Bad item");
+ return (0);
+ }
+
+ int eq = get_equip_slot(item);
+ if (eq < 0 || eq >= NUM_EQUIP)
+ {
+ mpr("Item is not equipped");
+ return (0);
+ }
+
+ bool result = false;
+ if (eq == EQ_WEAPON)
+ result = wield_weapon(true, -1);
+ else if (eq == EQ_LEFT_RING || eq == EQ_RIGHT_RING || eq == EQ_AMULET)
+ result = remove_ring(item->link);
+ else
+ result = takeoff_armour(item->link);
+ lua_pushboolean(ls, result);
+ return (1);
+}
+
+static int l_item_drop(lua_State *ls)
+{
+ if (you.turn_is_over)
+ return (0);
+
+ LUA_ITEM(item, 1);
+ if (!item || !in_inventory(*item))
+ return (0);
+
+ int eq = get_equip_slot(item);
+ if (eq >= 0 && eq < NUM_EQUIP)
+ {
+ lua_pushboolean(ls, false);
+ lua_pushstring(ls, "Can't drop worn items");
+ return (2);
+ }
+
+ int qty = item->quantity;
+ if (lua_isnumber(ls, 2))
+ {
+ int q = luaL_checkint(ls, 2);
+ if (q >= 1 && q <= item->quantity)
+ qty = q;
+ }
+ lua_pushboolean(ls, drop_item(item->link, qty));
+ return (1);
+}
+
+static int item_on_floor(const item_def &item, int x, int y);
+
+static item_def *dmx_get_item(lua_State *ls, int ndx, int subndx)
+{
+ if (lua_istable(ls, ndx))
+ {
+ lua_rawgeti(ls, ndx, subndx);
+ item_def *item = util_get_userdata<item_def>(ls, -1);
+ lua_pop(ls, 1);
+
+ return (item);
+ }
+ return util_get_userdata<item_def>(ls, ndx);
+}
+
+static int dmx_get_qty(lua_State *ls, int ndx, int subndx)
+{
+ int qty = -1;
+ if (lua_istable(ls, ndx))
+ {
+ lua_rawgeti(ls, ndx, subndx);
+ if (lua_isnumber(ls, -1))
+ qty = luaL_checkint(ls, -1);
+ lua_pop(ls, 1);
+ }
+ else if (lua_isnumber(ls, ndx))
+ {
+ qty = luaL_checkint(ls, ndx);
+ }
+ return (qty);
+}
+
+static bool l_item_pickup2(item_def *item, int qty)
+{
+ if (!item || in_inventory(*item))
+ return (false);
+
+ int floor_link = item_on_floor(*item, you.x_pos, you.y_pos);
+ if (floor_link == NON_ITEM)
+ return (false);
+
+ return pickup_single_item(floor_link, qty);
+}
+
+static int l_item_pickup(lua_State *ls)
+{
+ if (you.turn_is_over)
+ return (0);
+
+ if (lua_islightuserdata(ls, 1))
+ {
+ LUA_ITEM(item, 1);
+ int qty = item->quantity;
+ if (lua_isnumber(ls, 2))
+ qty = luaL_checkint(ls, 2);
+
+ if (l_item_pickup2(item, qty))
+ lua_pushnumber(ls, 1);
+ else
+ lua_pushnil(ls);
+ return (1);
+ }
+ else if (lua_istable(ls, 1))
+ {
+ int dropped = 0;
+ for (int i = 1; ; ++i)
+ {
+ lua_rawgeti(ls, 1, i);
+ item_def *item = dmx_get_item(ls, -1, 1);
+ int qty = dmx_get_qty(ls, -1, 2);
+ lua_pop(ls, 1);
+
+ if (l_item_pickup2(item, qty))
+ dropped++;
+ else
+ {
+ // Yes, we bail out on first failure.
+ break;
+ }
+ }
+ if (dropped)
+ lua_pushnumber(ls, dropped);
+ else
+ lua_pushnil(ls);
+ return (1);
+ }
+ return (0);
+}
+
+static int l_item_equipped(lua_State *ls)
+{
+ int eq = -1;
+ if (lua_isnumber(ls, 1))
+ eq = luaL_checkint(ls, 1);
+ else if (lua_isstring(ls, 1))
+ {
+ const char *eqname = lua_tostring(ls, 1);
+ if (!eqname)
+ return (0);
+ eq = equip_name_to_slot(eqname);
+ }
+
+ if (eq < 0 || eq >= NUM_EQUIP)
+ return (0);
+
+ if (you.equip[eq] != -1)
+ lua_pushlightuserdata(ls, &you.inv[you.equip[eq]]);
+ else
+ lua_pushnil(ls);
+
+ return (1);
+}
+
+static int l_item_class(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (item)
+ {
+ bool terse = false;
+ if (lua_isboolean(ls, 2))
+ terse = lua_toboolean(ls, 2);
+
+ std::string s = item_class_name(item->base_type, terse);
+ lua_pushstring(ls, s.c_str());
+ }
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+
+// FIXME: Fold this back into itemname.cc.
+static const char *ring_types[] = {
+ "regeneration",
+ "protection",
+ "protection from fire",
+ "poison resistance",
+ "protection from cold",
+ "strength",
+ "slaying",
+ "see invisible",
+ "invisibility",
+ "hunger",
+ "teleportation",
+ "evasion",
+ "sustain abilities",
+ "sustenance",
+ "dexterity",
+ "intelligence",
+ "wizardry",
+ "magical power",
+ "levitation",
+ "life protection",
+ "protection from magic",
+ "fire",
+ "ice",
+ "teleport control",
+};
+
+static const char *amulet_types[] = {
+ "rage", "resist slowing", "clarity", "warding", "resist corrosion",
+ "gourmand", "conservation", "controlled flight", "inaccuracy",
+ "resist mutation"
+};
+
+static int l_item_subtype(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (item)
+ {
+ if (item_type_known(*item))
+ {
+ const char *s = NULL;
+ if (item->base_type == OBJ_JEWELLERY)
+ {
+ if (item->sub_type < AMU_RAGE)
+ s = ring_types[item->sub_type];
+ else
+ s = amulet_types[ item->sub_type - AMU_RAGE ];
+ }
+
+ if (s)
+ lua_pushstring(ls, s);
+ else
+ lua_pushnil(ls);
+
+ lua_pushnumber(ls, item->sub_type);
+ return (2);
+ }
+ }
+
+ lua_pushnil(ls);
+ lua_pushnil(ls);
+ return (2);
+}
+
+static int l_item_cursed(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ bool cursed = item && item_ident(*item, ISFLAG_KNOW_CURSE)
+ && item_cursed(*item);
+ lua_pushboolean(ls, cursed);
+ return (1);
+}
+
+
+static int l_item_worn(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ int worn = get_equip_slot(item);
+ if (worn != -1)
+ lua_pushnumber(ls, worn);
+ else
+ lua_pushnil(ls);
+ if (worn != -1)
+ lua_pushstring(ls, equip_slot_to_name(worn));
+ else
+ lua_pushnil(ls);
+ return (2);
+}
+
+static int desc_code(const char *desc)
+{
+ if (!desc)
+ return DESC_PLAIN;
+
+ if (!strcmp("The", desc))
+ return DESC_CAP_THE;
+ else if (!strcmp("the", desc))
+ return DESC_NOCAP_THE;
+ else if (!strcmp("A", desc))
+ return DESC_CAP_A;
+ else if (!strcmp("a", desc))
+ return DESC_NOCAP_A;
+ else if (!strcmp("Your", desc))
+ return DESC_CAP_YOUR;
+ else if (!strcmp("your", desc))
+ return DESC_NOCAP_YOUR;
+ else if (!strcmp("its", desc))
+ return DESC_NOCAP_ITS;
+ else if (!strcmp("worn", desc))
+ return DESC_INVENTORY_EQUIP;
+ else if (!strcmp("inv", desc))
+ return DESC_INVENTORY;
+
+ return DESC_PLAIN;
+}
+
+static int l_item_name(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (item)
+ {
+ int ndesc = DESC_PLAIN;
+ if (lua_isstring(ls, 2))
+ ndesc = desc_code(lua_tostring(ls, 2));
+ bool terse = lua_toboolean(ls, 3);
+ char bufitemname[ITEMNAME_SIZE];
+ item_name(*item, ndesc, bufitemname, terse);
+
+ lua_pushstring(ls, bufitemname);
+ }
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+
+static int l_item_quantity(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ lua_pushnumber(ls, item? item->quantity : 0);
+ return (1);
+}
+
+static int l_item_inslot(lua_State *ls)
+{
+ int index = luaL_checkint(ls, 1);
+ if (index >= 0 && index < 52 && is_valid_item(you.inv[index]))
+ lua_pushlightuserdata(ls, &you.inv[index]);
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+
+static int l_item_slot(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (item)
+ {
+ int slot = in_inventory(*item)? item->link :
+ letter_to_index(item->slot);
+ lua_pushnumber(ls, slot);
+ }
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+
+static int l_item_ininventory(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ lua_pushboolean(ls, item && in_inventory(*item));
+ return (1);
+}
+
+static int l_item_equip_type(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (!item || !is_valid_item(*item))
+ return (0);
+
+ int eq = -1;
+ if (item->base_type == OBJ_WEAPONS || item->base_type == OBJ_STAVES)
+ eq = EQ_WEAPON;
+ else if (item->base_type == OBJ_ARMOUR)
+ eq = armour_equip_slot(*item);
+ else if (item->base_type == OBJ_JEWELLERY)
+ eq = item->sub_type >= AMU_RAGE? EQ_AMULET : EQ_RINGS;
+
+ if (eq != -1)
+ lua_pushnumber(ls, eq);
+ else
+ lua_pushnil(ls);
+ if (eq != -1)
+ lua_pushstring(ls, equip_slot_to_name(eq));
+ else
+ lua_pushnil(ls);
+ return (2);
+}
+
+static int l_item_weap_skill(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (!item || !is_valid_item(*item))
+ return (0);
+
+ int skill = weapon_skill( item->base_type, item->sub_type );
+ if (skill == SK_FIGHTING)
+ return (0);
+
+ lua_pushstring(ls, skill_name(skill));
+ return (1);
+}
+
+static int l_item_artifact(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (!item || !is_valid_item(*item))
+ return (0);
+
+ lua_pushboolean(ls, item_ident(*item, ISFLAG_KNOW_PROPERTIES)
+ && (is_random_artefact(*item) || is_fixed_artefact(*item)));
+ return (1);
+}
+
+static int l_item_branded(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (!item || !is_valid_item(*item) || !item_ident(*item, ISFLAG_KNOW_TYPE))
+ return (0);
+
+ bool branded = false;
+ switch (item->base_type)
+ {
+ case OBJ_WEAPONS:
+ branded = get_weapon_brand(*item) != SPWPN_NORMAL;
+ break;
+ case OBJ_ARMOUR:
+ branded = get_armour_ego_type(*item) != SPARM_NORMAL;
+ break;
+ case OBJ_MISSILES:
+ branded = get_ammo_brand(*item) != SPMSL_NORMAL;
+ break;
+ }
+ lua_pushboolean(ls, branded);
+ return (1);
+}
+
+static const struct luaL_reg item_lib[] =
+{
+ { "artifact", l_item_artifact },
+ { "branded", l_item_branded },
+ { "class", l_item_class },
+ { "subtype", l_item_subtype },
+ { "cursed", l_item_cursed },
+ { "worn", l_item_worn },
+ { "name", l_item_name },
+ { "quantity", l_item_quantity },
+ { "inslot", l_item_inslot },
+ { "slot", l_item_slot },
+ { "ininventory", l_item_ininventory },
+ { "inventory", l_item_inventory },
+ { "letter_to_index", l_item_letter_to_index },
+ { "index_to_letter", l_item_index_to_letter },
+ { "swap_slots", l_item_swap_slots },
+ { "wield", l_item_wield },
+ { "wear", l_item_wear },
+ { "puton", l_item_puton },
+ { "remove", l_item_remove },
+ { "drop", l_item_drop },
+ { "pickup", l_item_pickup },
+ { "equipped_at", l_item_equipped },
+ { "equip_type", l_item_equip_type },
+ { "weap_skill", l_item_weap_skill },
+
+ { NULL, NULL },
+};
+
+void lua_open_item(lua_State *ls)
+{
+ luaL_openlib(ls, "item", item_lib, 0);
+}
+
+/////////////////////////////////////////////////////////////////////
+// Food information. Some of this information is spoily (such as whether
+// a given chunk is poisonous), but that can't be helped.
+//
+
+static int food_do_eat(lua_State *ls)
+{
+ bool eaten = false;
+ if (!you.turn_is_over)
+ eaten = eat_food(false);
+ lua_pushboolean(ls, eaten);
+ return (1);
+}
+
+static int food_prompt_floor(lua_State *ls)
+{
+ bool eaten = false;
+ if (!you.turn_is_over && (eaten = eat_from_floor()))
+ burden_change();
+ lua_pushboolean(ls, eaten);
+ return (1);
+}
+
+static int food_prompt_inventory(lua_State *ls)
+{
+ bool eaten = false;
+ if (!you.turn_is_over)
+ eaten = prompt_eat_from_inventory();
+ lua_pushboolean(ls, eaten);
+ return (1);
+}
+
+static int food_can_eat(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ bool hungercheck = true;
+
+ if (lua_isboolean(ls, 2))
+ hungercheck = lua_toboolean(ls, 2);
+
+ bool edible = item && can_ingest(item->base_type,
+ item->sub_type,
+ true,
+ true,
+ hungercheck);
+ lua_pushboolean(ls, edible);
+ return (1);
+}
+
+static int item_on_floor(const item_def &item, int x, int y)
+{
+ // Check if the item is on the floor and reachable
+ for (int link = igrd[x][y]; link != NON_ITEM; link = mitm[link].link)
+ {
+ if (&mitm[link] == &item)
+ return (link);
+ }
+ return (NON_ITEM);
+}
+
+static bool eat_item(const item_def &item)
+{
+ if (in_inventory(item))
+ {
+ eat_from_inventory(item.link);
+ burden_change();
+ you.turn_is_over = 1;
+
+ return (true);
+ }
+ else
+ {
+ int ilink = item_on_floor(item, you.x_pos, you.y_pos);
+
+ if (ilink != NON_ITEM)
+ {
+ eat_floor_item(ilink);
+ return (true);
+ }
+ return (false);
+ }
+}
+
+static int food_eat(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+
+ bool eaten = false;
+ if (!you.turn_is_over)
+ {
+ // When we get down to eating, we don't care if the eating is courtesy
+ // an un-ided amulet of the gourmand.
+ bool edible = item && can_ingest(item->base_type,
+ item->sub_type,
+ false,
+ false);
+ if (edible)
+ eaten = eat_item(*item);
+ }
+ lua_pushboolean(ls, eaten);
+ return (1);
+}
+
+// Giving away chunk type information is spoily.
+/*
+static int food_chunktype(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ if (item && item->base_type == OBJ_FOOD && item->sub_type == FOOD_CHUNK)
+ {
+ int mons_type = item->plus;
+ int chunk_type = mons_corpse_thingy(mons_type);
+ const char *schunktype = "unknown";
+ switch (chunk_type)
+ {
+ case CE_HCL:
+ case CE_MUTAGEN_GOOD:
+ case CE_MUTAGEN_BAD:
+ case CE_MUTAGEN_RANDOM:
+ schunktype = "mutagenic";
+ break;
+ case CE_POISONOUS:
+ schunktype = "poisonous";
+ break;
+ case CE_CONTAMINATED:
+ schunktype = "contaminated";
+ break;
+ case CE_CLEAN:
+ schunktype = "clean";
+ break;
+ }
+ lua_pushstring(ls, schunktype);
+ }
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+*/
+
+static int food_rotting(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ bool rotting = false;
+ if (item && item->base_type == OBJ_FOOD && item->sub_type == FOOD_CHUNK)
+ {
+ rotting = item->special < 100;
+ }
+ lua_pushboolean(ls, rotting);
+ return (1);
+}
+
+static int food_ischunk(lua_State *ls)
+{
+ LUA_ITEM(item, 1);
+ lua_pushboolean(ls,
+ item && item->base_type == OBJ_FOOD
+ && item->sub_type == FOOD_CHUNK);
+ return (1);
+}
+
+static const struct luaL_reg food_lib[] =
+{
+ { "do_eat", food_do_eat },
+ { "prompt_floor", food_prompt_floor },
+ { "prompt_inventory", food_prompt_inventory },
+ { "can_eat", food_can_eat },
+ { "eat", food_eat },
+ { "rotting", food_rotting },
+ { "ischunk", food_ischunk },
+ { NULL, NULL },
+};
+
+void lua_open_food(lua_State *ls)
+{
+ luaL_openlib(ls, "food", food_lib, 0);
+}
+
+/////////////////////////////////////////////////////////////////////
+// General game bindings.
+//
+
+static int crawl_mpr(lua_State *ls)
+{
+ const char *message = luaL_checkstring(ls, 1);
+ if (!message)
+ return (0);
+
+ int ch = MSGCH_PLAIN;
+ const char *channel = lua_tostring(ls, 2);
+ if (channel)
+ ch = str_to_channel(channel);
+ if (ch == -1)
+ ch = MSGCH_PLAIN;
+
+ mpr(message, ch);
+
+ return (0);
+}
+
+LUAWRAP(crawl_mesclr, mesclr())
+LUAWRAP(crawl_redraw_screen, redraw_screen())
+
+static int crawl_input_line(lua_State *ls)
+{
+ // This is arbitrary, but anybody entering so many characters is psychotic.
+ char linebuf[500];
+
+ get_input_line(linebuf, sizeof linebuf);
+ lua_pushstring(ls, linebuf);
+ return (1);
+}
+
+static int crawl_c_input_line(lua_State *ls)
+{
+ char linebuf[500];
+
+ bool valid = cancelable_get_line(linebuf, sizeof linebuf);
+ if (valid)
+ lua_pushstring(ls, linebuf);
+ else
+ lua_pushnil(ls);
+ return (1);
+}
+
+LUARET1(crawl_getch, number, getch())
+LUARET1(crawl_kbhit, number, kbhit())
+LUAWRAP(crawl_flush_input, flush_input_buffer(FLUSH_LUA))
+
+static void crawl_sendkeys_proc(lua_State *ls, int argi)
+{
+ if (lua_isstring(ls, argi))
+ {
+ const char *keys = luaL_checkstring(ls, argi);
+ if (!keys)
+ return;
+
+ for ( ; *keys; ++keys)
+ macro_buf_add(*keys);
+ }
+ else if (lua_istable(ls, argi))
+ {
+ for (int i = 1; ; ++i)
+ {
+ lua_rawgeti(ls, argi, i);
+ if (lua_isnil(ls, -1))
+ {
+ lua_pop(ls, 1);
+ return;
+ }
+
+ crawl_sendkeys_proc(ls, lua_gettop(ls));
+ lua_pop(ls, 1);
+ }
+ }
+ else if (lua_isnumber(ls, argi))
+ {
+ int key = luaL_checkint(ls, argi);
+ macro_buf_add(key);
+ }
+}
+
+static int crawl_sendkeys(lua_State *ls)
+{
+ int top = lua_gettop(ls);
+ for (int i = 1; i <= top; ++i)
+ crawl_sendkeys_proc(ls, i);
+ return (0);
+}
+
+static int crawl_playsound(lua_State *ls)
+{
+ const char *sf = luaL_checkstring(ls, 1);
+ if (!sf)
+ return (0);
+ play_sound(sf);
+ return (0);
+}
+
+static int crawl_runmacro(lua_State *ls)
+{
+ const char *macroname = luaL_checkstring(ls, 1);
+ if (!macroname)
+ return (0);
+ run_macro(macroname);
+ return (0);
+}
+
+static int crawl_setopt(lua_State *ls)
+{
+ if (!lua_isstring(ls, 1))
+ return (0);
+
+ const char *s = lua_tostring(ls, 1);
+ if (s)
+ {
+ // Note that the conditional script can contain nested Lua[ ]Lua code.
+ read_options(s, true);
+ }
+
+ return (0);
+}
+
+static int crawl_bindkey(lua_State *ls)
+{
+ const char *s = NULL;
+ if (lua_isstring(ls, 1))
+ {
+ s = lua_tostring(ls, 1);
+ }
+
+ if (!s || !lua_isfunction(ls, 2) || !lua_gettop(ls) == 2)
+ return (0);
+
+ lua_pushvalue(ls, 2);
+ std::string name = clua.setuniqregistry();
+ if (lua_gettop(ls) != 2)
+ {
+ fprintf(stderr, "Stack top has changed!\n");
+ lua_settop(ls, 2);
+ }
+ macro_userfn(s, name.c_str());
+ return (0);
+}
+
+static int crawl_msgch_num(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1);
+ if (!s)
+ return (0);
+ int ch = str_to_channel(s);
+ if (ch == -1)
+ return (0);
+
+ lua_pushnumber(ls, ch);
+ return (1);
+}
+
+static int crawl_msgch_name(lua_State *ls)
+{
+ int num = luaL_checkint(ls, 1);
+ std::string name = channel_to_str(num);
+ lua_pushstring(ls, name.c_str());
+ return (1);
+}
+
+#define REGEX_METATABLE "crawl.regex"
+#define MESSF_METATABLE "crawl.messf"
+
+static int crawl_regex(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1);
+ if (!s)
+ return (0);
+
+
+ text_pattern **tpudata =
+ clua_new_userdata< text_pattern* >(ls, REGEX_METATABLE);
+ if (tpudata)
+ {
+ *tpudata = new text_pattern(s);
+ return (1);
+ }
+ return (0);
+}
+
+static int crawl_regex_find(lua_State *ls)
+{
+ text_pattern **pattern =
+ clua_get_userdata< text_pattern* >(ls, REGEX_METATABLE);
+ if (!pattern)
+ return (0);
+
+ const char *text = luaL_checkstring(ls, -1);
+ if (!text)
+ return (0);
+
+ lua_pushboolean(ls, (*pattern)->matches(text));
+ return (1);
+}
+
+static int crawl_regex_gc(lua_State *ls)
+{
+ text_pattern **pattern =
+ clua_get_userdata< text_pattern* >(ls, REGEX_METATABLE);
+ if (pattern)
+ delete *pattern;
+ return (0);
+}
+
+static const luaL_reg crawl_regex_ops[] =
+{
+ { "matches", crawl_regex_find },
+ { NULL, NULL }
+};
+
+static int crawl_message_filter(lua_State *ls)
+{
+ const char *pattern = luaL_checkstring(ls, 1);
+ if (!pattern)
+ return (0);
+
+ int num = lua_isnumber(ls, 2)? luaL_checkint(ls, 2) : -1;
+ message_filter **mf =
+ clua_new_userdata< message_filter* >( ls, MESSF_METATABLE );
+ if (mf)
+ {
+ *mf = new message_filter( num, pattern );
+ return (1);
+ }
+ return (0);
+}
+
+static int crawl_messf_matches(lua_State *ls)
+{
+ message_filter **mf =
+ clua_get_userdata< message_filter* >(ls, MESSF_METATABLE);
+ if (!mf)
+ return (0);
+
+ const char *pattern = luaL_checkstring(ls, 2);
+ int ch = luaL_checkint(ls, 3);
+ if (pattern)
+ {
+ bool filt = (*mf)->is_filtered(ch, pattern);
+ lua_pushboolean(ls, filt);
+ return (1);
+ }
+ return (0);
+}
+
+static int crawl_messf_gc(lua_State *ls)
+{
+ message_filter **pattern =
+ clua_get_userdata< message_filter* >(ls, REGEX_METATABLE);
+ if (pattern)
+ delete *pattern;
+ return (0);
+}
+
+static const luaL_reg crawl_messf_ops[] =
+{
+ { "matches", crawl_messf_matches },
+ { NULL, NULL }
+};
+
+static int crawl_trim(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1);
+ if (!s)
+ return (0);
+ std::string text = s;
+ trim_string(text);
+ lua_pushstring(ls, text.c_str());
+ return (1);
+}
+
+static int crawl_split(lua_State *ls)
+{
+ const char *s = luaL_checkstring(ls, 1),
+ *token = luaL_checkstring(ls, 2);
+ if (!s || !token)
+ return (0);
+
+ std::vector<std::string> segs = split_string(token, s);
+ lua_newtable(ls);
+ for (int i = 0, count = segs.size(); i < count; ++i)
+ {
+ lua_pushstring(ls, segs[i].c_str());
+ lua_rawseti(ls, -2, i + 1);
+ }
+ return (1);
+}
+
+static const struct luaL_reg crawl_lib[] =
+{
+ { "mpr", crawl_mpr },
+ { "mesclr", crawl_mesclr },
+ { "redraw_screen", crawl_redraw_screen },
+ { "input_line", crawl_input_line },
+ { "c_input_line", crawl_c_input_line},
+ { "getch", crawl_getch },
+ { "kbhit", crawl_kbhit },
+ { "flush_input", crawl_flush_input },
+ { "sendkeys", crawl_sendkeys },
+ { "playsound", crawl_playsound },
+ { "runmacro", crawl_runmacro },
+ { "bindkey", crawl_bindkey },
+ { "setopt", crawl_setopt },
+ { "msgch_num", crawl_msgch_num },
+ { "msgch_name", crawl_msgch_name },
+
+ { "regex", crawl_regex },
+ { "message_filter", crawl_message_filter },
+ { "trim", crawl_trim },
+ { "split", crawl_split },
+ { NULL, NULL },
+};
+
+void lua_open_crawl(lua_State *ls)
+{
+ clua_register_metatable(ls, REGEX_METATABLE, crawl_regex_ops,
+ crawl_regex_gc);
+ clua_register_metatable(ls, MESSF_METATABLE, crawl_messf_ops,
+ crawl_messf_gc);
+
+ luaL_openlib(ls, "crawl", crawl_lib, 0);
+}
+
+///////////////////////////////////////////////////////////
+// File operations
+
+
+static const struct luaL_reg file_lib[] =
+{
+ { "write", CLua::file_write },
+ { NULL, NULL },
+};
+
+void lua_open_file(lua_State *ls)
+{
+ luaL_openlib(ls, "file", file_lib, 0);
+}
+
+
+////////////////////////////////////////////////////////////////
+// Option handling
+
+typedef int (*ohandler)(lua_State *ls, const char *name, void *data, bool get);
+struct option_handler
+{
+ const char *option;
+ void *data;
+ ohandler handler;
+};
+
+static int option_hboolean(lua_State *ls, const char *name, void *data,
+ bool get)
+{
+ if (get)
+ {
+ lua_pushboolean(ls, *static_cast<bool*>( data ));
+ return (1);
+ }
+ else
+ {
+ if (lua_isboolean(ls, 3))
+ *static_cast<bool*>( data ) = lua_toboolean(ls, 3);
+ return (0);
+ }
+}
+
+static option_handler handlers[] =
+{
+ // Boolean options come first
+ { "easy_open", &Options.easy_open, option_hboolean },
+ { "verbose_dump", &Options.verbose_dump, option_hboolean },
+ { "detailed_stat_dump", &Options.detailed_stat_dump, option_hboolean },
+ { "colour_map", &Options.colour_map, option_hboolean },
+ { "clean_map", &Options.clean_map, option_hboolean },
+ { "show_uncursed", &Options.show_uncursed, option_hboolean },
+ { "always_greet", &Options.always_greet, option_hboolean },
+ { "easy_open", &Options.easy_open, option_hboolean },
+ { "easy_armour", &Options.easy_armour, option_hboolean },
+ { "easy_butcher", &Options.easy_butcher, option_hboolean },
+ { "terse_hand", &Options.terse_hand, option_hboolean },
+ { "delay_message_clear", &Options.delay_message_clear, option_hboolean },
+ { "no_dark_brand", &Options.no_dark_brand, option_hboolean },
+ { "auto_list", &Options.auto_list, option_hboolean },
+ { "lowercase_invocations", &Options.lowercase_invocations,
+ option_hboolean },
+ { "pickup_thrown", &Options.pickup_thrown, option_hboolean },
+ { "pickup_dropped", &Options.pickup_dropped, option_hboolean },
+ { "show_waypoints", &Options.show_waypoints, option_hboolean },
+ { "item_colour", &Options.item_colour, option_hboolean },
+ { "target_zero_exp", &Options.target_zero_exp, option_hboolean },
+ { "target_wrap", &Options.target_wrap, option_hboolean },
+ { "easy_exit_menu", &Options.easy_exit_menu, option_hboolean },
+ { "dos_use_background_intensity", &Options.dos_use_background_intensity,
+ option_hboolean },
+};
+
+static const option_handler *get_handler(const char *optname)
+{
+ if (optname)
+ {
+ for (int i = 0, count = sizeof(handlers) / sizeof(*handlers);
+ i < count; ++i)
+ {
+ if (!strcmp(handlers[i].option, optname))
+ return &handlers[i];
+ }
+ }
+ return (NULL);
+}
+
+static int option_get(lua_State *ls)
+{
+ const char *opt = luaL_checkstring(ls, 2);
+ if (!opt)
+ return (0);
+
+ // Is this a Lua named option?
+ game_options::opt_map::iterator i = Options.named_options.find(opt);
+ if (i != Options.named_options.end())
+ {
+ const std::string &ov = i->second;
+ lua_pushstring(ls, ov.c_str());
+ return (1);
+ }
+
+ const option_handler *oh = get_handler(opt);
+ if (oh)
+ return (oh->handler(ls, opt, oh->data, true));
+
+ return (0);
+}
+
+static int option_set(lua_State *ls)
+{
+ const char *opt = luaL_checkstring(ls, 2);
+ if (!opt)
+ return (0);
+
+ const option_handler *oh = get_handler(opt);
+ if (oh)
+ oh->handler(ls, opt, oh->data, false);
+
+ return (0);
+}
+
+#define OPT_METATABLE "options.optaccess"
+void lua_open_options(lua_State *ls)
+{
+ int top = lua_gettop(ls);
+
+ luaL_newmetatable(ls, OPT_METATABLE);
+ lua_pushstring(ls, "__index");
+ lua_pushcfunction(ls, option_get);
+ lua_settable(ls, -3);
+
+ luaL_getmetatable(ls, OPT_METATABLE);
+ lua_pushstring(ls, "__newindex");
+ lua_pushcfunction(ls, option_set);
+ lua_settable(ls, -3);
+
+ lua_settop(ls, top);
+
+ // Create dummy userdata to front for our metatable
+ int *dummy = static_cast<int *>( lua_newuserdata(ls, sizeof(int)) );
+ // Mystic number
+ *dummy = 42;
+
+ luaL_getmetatable(ls, OPT_METATABLE);
+ lua_setmetatable(ls, -2);
+
+ clua.setglobal("options");
+}
+
+/////////////////////////////////////////////////////////////////////
+// Monster handling
+
+#define MONS_METATABLE "monster.monsaccess"
+struct MonsterWrap
+{
+ monsters *mons;
+ long turn;
+};
+
+static int l_mons_name(lua_State *ls, monsters *mons, const char *attr)
+{
+ char monnamebuf[ITEMNAME_SIZE]; // Le sigh.
+ moname(mons->type, true, DESC_PLAIN, monnamebuf);
+ lua_pushstring(ls, monnamebuf);
+ return (1);
+}
+
+static int l_mons_x(lua_State *ls, monsters *mons, const char *attr)
+{
+ lua_pushnumber(ls, int(mons->x) - int(you.x_pos));
+ return (1);
+}
+
+static int l_mons_y(lua_State *ls, monsters *mons, const char *attr)
+{
+ lua_pushnumber(ls, int(mons->y) - int(you.y_pos));
+ return (1);
+}
+
+struct MonsAccessor {
+ const char *attribute;
+ int (*accessor)(lua_State *ls, monsters *mons, const char *attr);
+};
+
+static MonsAccessor mons_attrs[] =
+{
+ { "name", l_mons_name },
+ { "x" , l_mons_x },
+ { "y" , l_mons_y },
+};
+
+static int monster_get(lua_State *ls)
+{
+ MonsterWrap *mw = clua_get_userdata< MonsterWrap >(ls, MONS_METATABLE);
+ if (!mw || mw->turn != you.num_turns || !mw->mons)
+ return (0);
+
+ const char *attr = luaL_checkstring(ls, 2);
+ if (!attr)
+ return (0);
+
+ for (unsigned i = 0; i < sizeof(mons_attrs) / sizeof(mons_attrs[0]); ++i)
+ {
+ if (!strcmp(attr, mons_attrs[i].attribute))
+ return (mons_attrs[i].accessor(ls, mw->mons, attr));
+ }
+
+ return (0);
+}
+
+// We currently permit no set operations on monsters
+static int monster_set(lua_State *ls)
+{
+ return (0);
+}
+
+static int push_activity_interrupt(lua_State *ls, activity_interrupt_t *t)
+{
+ if (!t->data)
+ {
+ lua_pushnil(ls);
+ return 0;
+ }
+
+ switch (t->apt)
+ {
+ case AIP_HP_LOSS:
+ {
+ const ait_hp_loss *ahl = (const ait_hp_loss *) t->data;
+ lua_pushnumber(ls, ahl->hp);
+ lua_pushnumber(ls, ahl->hurt_type);
+ return 1;
+ }
+ case AIP_INT:
+ lua_pushnumber(ls, *(const int *) t->data);
+ break;
+ case AIP_STRING:
+ lua_pushstring(ls, (const char *) t->data);
+ break;
+ case AIP_MONSTER:
+ // FIXME: We're casting away the const...
+ push_monster(ls, (monsters *) t->data);
+ break;
+ default:
+ lua_pushnil(ls);
+ break;
+ }
+ return 0;
+}
+
+static void push_monster(lua_State *ls, monsters *mons)
+{
+ MonsterWrap *mw = clua_new_userdata< MonsterWrap >(ls, MONS_METATABLE);
+ mw->turn = you.num_turns;
+ mw->mons = mons;
+}
+
+void lua_open_monsters(lua_State *ls)
+{
+ luaL_newmetatable(ls, MONS_METATABLE);
+ lua_pushstring(ls, "__index");
+ lua_pushcfunction(ls, monster_get);
+ lua_settable(ls, -3);
+
+ lua_pushstring(ls, "__newindex");
+ lua_pushcfunction(ls, monster_set);
+ lua_settable(ls, -3);
+
+ // Pop the metatable off the stack.
+ lua_pop(ls, 1);
+}
+
+//////////////////////////////////////////////////////////////////////
+// Miscellaneous globals
+
+#define PATTERN_FLUSH_CEILING 100
+
+typedef CHMAP<std::string, text_pattern> pattern_map;
+static pattern_map pattern_cache;
+
+static text_pattern &get_text_pattern(const std::string &s, bool checkcase)
+{
+ pattern_map::iterator i = pattern_cache.find(s);
+ if (i != pattern_cache.end())
+ return i->second;
+
+ if (pattern_cache.size() > PATTERN_FLUSH_CEILING)
+ pattern_cache.clear();
+
+ pattern_cache[s] = text_pattern(s, !checkcase);
+ return pattern_cache[s];
+}
+
+static int lua_pmatch(lua_State *ls)
+{
+ const char *pattern = luaL_checkstring(ls, 1);
+ if (!pattern)
+ return (0);
+
+ const char *text = luaL_checkstring(ls, 2);
+ if (!text)
+ return (0);
+
+ bool checkcase = true;
+ if (lua_isboolean(ls, 3))
+ checkcase = lua_toboolean(ls, 3);
+
+ text_pattern &tp = get_text_pattern(pattern, checkcase);
+ lua_pushboolean( ls, tp.matches(text) );
+ return (1);
+}
+
+void lua_open_globals(lua_State *ls)
+{
+ lua_pushcfunction(ls, lua_pmatch);
+ lua_setglobal(ls, "pmatch");
+}
+
+
+////////////////////////////////////////////////////////////////////////
+// lua_text_pattern
+
+// We could simplify this a great deal by just using lex and yacc, but I
+// don't know if we want to introduce them.
+
+struct lua_pat_op {
+ const char *token;
+ const char *luatok;
+
+ bool pretext; // Does this follow a pattern?
+ bool posttext; // Is this followed by a pattern?
+};
+
+static lua_pat_op pat_ops[] = {
+ { "<<", " ( ", false, true },
+ { ">>", " ) ", true, false },
+ { "!!", " not ", false, true },
+ { "==", " == ", true, true },
+ { "^^", " ~= ", true, true },
+ { "&&", " and ", true, true },
+ { "||", " or ", true, true },
+};
+
+unsigned long lua_text_pattern::lfndx = 0L;
+
+bool lua_text_pattern::is_lua_pattern(const std::string &s)
+{
+ for (int i = 0, size = sizeof(pat_ops) / sizeof(*pat_ops);
+ i < size; ++i)
+ {
+ if (s.find(pat_ops[i].token) != std::string::npos)
+ return (true);
+ }
+ return (false);
+}
+
+lua_text_pattern::lua_text_pattern(const std::string &_pattern)
+ : translated(false), isvalid(true), pattern(_pattern), lua_fn_name()
+{
+ lua_fn_name = new_fn_name();
+}
+
+lua_text_pattern::~lua_text_pattern()
+{
+ if (translated && !lua_fn_name.empty())
+ {
+ lua_State *ls = clua;
+ if (ls)
+ {
+ lua_pushnil(ls);
+ clua.setglobal(lua_fn_name.c_str());
+ }
+ }
+}
+
+bool lua_text_pattern::valid() const
+{
+ return translated? isvalid : translate();
+}
+
+bool lua_text_pattern::matches( const std::string &s ) const
+{
+ if (isvalid && !translated)
+ translate();
+
+ if (!isvalid)
+ return (false);
+
+ return clua.callbooleanfn(false, lua_fn_name.c_str(), "s", s.c_str());
+}
+
+void lua_text_pattern::pre_pattern(std::string &pat, std::string &fn) const
+{
+ // Trim trailing spaces
+ pat.erase( pat.find_last_not_of(" \t\n\r") + 1 );
+
+ fn += " pmatch([[";
+ fn += pat;
+ fn += "]], text, false) ";
+
+ pat.clear();
+}
+
+void lua_text_pattern::post_pattern(std::string &pat, std::string &fn) const
+{
+ pat.erase( 0, pat.find_first_not_of(" \t\n\r") );
+
+ fn += " pmatch([[";
+ fn += pat;
+ fn += "]], text, false) ";
+
+ pat.clear();
+}
+
+std::string lua_text_pattern::new_fn_name()
+{
+ char buf[100];
+ snprintf(buf, sizeof buf, "__ch_stash_search_%lu", lfndx++);
+ return (buf);
+}
+
+bool lua_text_pattern::translate() const
+{
+ if (translated || !isvalid)
+ return false;
+
+ std::string textp;
+ std::string luafn;
+ const lua_pat_op *currop = NULL;
+ for (std::string::size_type i = 0; i < pattern.length(); ++i)
+ {
+ bool match = false;
+ for (unsigned p = 0; p < sizeof pat_ops / sizeof *pat_ops; ++p)
+ {
+ const lua_pat_op &lop = pat_ops[p];
+ if (pattern.find(lop.token, i) == i)
+ {
+ match = true;
+ if (lop.pretext && (!currop || currop->posttext))
+ {
+ if (currop)
+ textp.erase(0, textp.find_first_not_of(" \r\n\t"));
+ pre_pattern(textp, luafn);
+ }
+
+ currop = &lop;
+ luafn += lop.luatok;
+
+ i += strlen( lop.token ) - 1;
+
+ break;
+ }
+ }
+
+ if (match)
+ continue;
+
+ textp += pattern[i];
+ }
+
+ if (currop && currop->posttext)
+ post_pattern(textp, luafn);
+
+ luafn = "function " + lua_fn_name + "(text) return " + luafn + " end";
+
+ const_cast<lua_text_pattern *>(this)->translated = true;
+
+ int err = clua.execstring( luafn.c_str(), "stash-search" );
+ if (err)
+ {
+ lua_text_pattern *self = const_cast<lua_text_pattern *>(this);
+ self->isvalid = self->translated = false;
+ }
+
+ return translated;
+}
+
+#endif // CLUA_BINDINGS
diff --git a/trunk/source/clua.h b/trunk/source/clua.h
new file mode 100755
index 0000000000..88f0f45cc0
--- /dev/null
+++ b/trunk/source/clua.h
@@ -0,0 +1,122 @@
+#ifndef __CLUA_H__
+#define __CLUA_H__
+
+#include "AppHdr.h"
+
+#ifdef CLUA_BINDINGS
+
+extern "C" {
+#include <lua.h>
+#include <lauxlib.h>
+#include <lualib.h>
+}
+
+#include <cstdio>
+#include <cstdarg>
+#include <string>
+#include <set>
+
+#include "libutil.h"
+#include "externs.h"
+
+class CLua
+{
+public:
+ CLua();
+ ~CLua();
+
+ lua_State *state();
+
+ operator lua_State * ()
+ {
+ return state();
+ }
+
+ void save(const char *filename);
+
+ void setglobal(const char *name);
+ void getglobal(const char *name);
+
+ // Assigns the value on top of the stack to a unique name in the registry
+ // and returns the name.
+ std::string setuniqregistry();
+
+ void setregistry(const char *name);
+ void getregistry(const char *name);
+
+ int execstring(const char *str, const char *context = "init.txt");
+ int execfile(const char *filename);
+
+ bool callbooleanfn(bool defval, const char *fn, const char *params, ...);
+ bool callfn(const char *fn, int nargs, int nret = 1);
+ bool callfn(const char *fn, const char *params, ...);
+ void fnreturns(const char *params, ...);
+ bool runhook(const char *hook, const char *params, ...);
+
+ static int file_write(lua_State *ls);
+
+ std::string error;
+private:
+ lua_State *_state;
+ typedef std::set<std::string> sfset;
+ sfset sourced_files;
+ unsigned long uniqindex;
+
+ void init_lua();
+ void set_error(int err, lua_State *ls = NULL);
+ void load_cmacro();
+ void load_chooks();
+
+ void vfnreturns(const char *par, va_list va);
+
+ bool proc_returns(const char *par) const;
+
+ bool calltopfn(lua_State *ls, const char *format, va_list args,
+ int retc = -1, va_list *fnr = NULL);
+
+ int push_args(lua_State *ls, const char *format, va_list args,
+ va_list *cpto = NULL);
+ int return_count(lua_State *ls, const char *format);
+
+ struct CLuaSave
+ {
+ const char *filename;
+ FILE *handle;
+
+ FILE *get_file();
+ };
+};
+
+class lua_text_pattern : public base_pattern
+{
+public:
+ lua_text_pattern(const std::string &pattern);
+ ~lua_text_pattern();
+
+ bool valid() const;
+ bool matches(const std::string &s) const;
+
+ static bool is_lua_pattern(const std::string &s);
+
+private:
+ bool translated;
+ bool isvalid;
+ std::string pattern;
+ std::string lua_fn_name;
+
+ static unsigned long lfndx;
+
+ bool translate() const;
+ void pre_pattern(std::string &pat, std::string &fn) const;
+ void post_pattern(std::string &pat, std::string &fn) const;
+
+ static std::string new_fn_name();
+};
+
+extern CLua clua;
+
+void lua_set_exclusive_item(const item_def *item = NULL);
+
+#endif // CLUA_BINDINGS
+
+#endif
diff --git a/trunk/source/command.cc b/trunk/source/command.cc
index 977dbaf035..25644bdb0b 100644
--- a/trunk/source/command.cc
+++ b/trunk/source/command.cc
@@ -25,6 +25,7 @@
#include "itemname.h"
#include "item_use.h"
#include "items.h"
+#include "menu.h"
#include "ouch.h"
#include "spl-cast.h"
#include "spl-util.h"
@@ -38,13 +39,41 @@ static void adjust_ability(void);
void quit_game(void)
{
- if (yesno("Really quit?", false))
+ if (yesno("Really quit?", false, 'n'))
ouch(-9999, 0, KILLED_BY_QUITTING);
} // end quit_game()
+static const char *features[] = {
+ "",
+#ifdef STASH_TRACKING
+ "Stash-tracking",
+#endif
+
+#ifdef CLUA_BINDINGS
+ "Lua",
+#endif
+
+#if defined(REGEX_POSIX)
+ "POSIX regexps",
+#elif defined(REGEX_PCRE)
+ "PCRE regexps",
+#else
+ "Glob patterns",
+#endif
+};
+
void version(void)
{
mpr( "This is Dungeon Crawl " VERSION " (Last build " BUILD_DATE ")." );
+
+ std::string feats = "Features: ";
+ for (int i = 1, size = sizeof features / sizeof *features; i < size; ++i)
+ {
+ if (i > 1)
+ feats += ", ";
+ feats += features[i];
+ }
+ mpr(feats.c_str());
} // end version()
void adjust(void)
@@ -65,6 +94,41 @@ void adjust(void)
canned_msg( MSG_HUH );
} // end adjust()
+void swap_inv_slots(int from_slot, int to_slot, bool verbose)
+{
+ // swap items
+ item_def tmp = you.inv[to_slot];
+ you.inv[to_slot] = you.inv[from_slot];
+ you.inv[from_slot] = tmp;
+
+ you.inv[from_slot].link = from_slot;
+ you.inv[to_slot].link = to_slot;
+
+ for (int i = 0; i < NUM_EQUIP; i++)
+ {
+ if (you.equip[i] == from_slot)
+ you.equip[i] = to_slot;
+ else if (you.equip[i] == to_slot)
+ you.equip[i] = from_slot;
+ }
+
+ if (verbose)
+ {
+ char str_pass[ ITEMNAME_SIZE ];
+ in_name( to_slot, DESC_INVENTORY_EQUIP, str_pass );
+ mpr( str_pass );
+
+ if (is_valid_item( you.inv[from_slot] ))
+ {
+ in_name( from_slot, DESC_INVENTORY_EQUIP, str_pass );
+ mpr( str_pass );
+ }
+ }
+
+ if (to_slot == you.equip[EQ_WEAPON] || from_slot == you.equip[EQ_WEAPON])
+ you.wield_change = true;
+}
+
static void adjust_item(void)
{
int from_slot, to_slot;
@@ -93,33 +157,7 @@ static void adjust_item(void)
return;
}
- // swap items
- item_def tmp = you.inv[to_slot];
- you.inv[to_slot] = you.inv[from_slot];
- you.inv[from_slot] = tmp;
-
- you.inv[from_slot].link = from_slot;
- you.inv[to_slot].link = to_slot;
-
- for (int i = 0; i < NUM_EQUIP; i++)
- {
- if (you.equip[i] == from_slot)
- you.equip[i] = to_slot;
- else if (you.equip[i] == to_slot)
- you.equip[i] = from_slot;
- }
-
- in_name( to_slot, DESC_INVENTORY_EQUIP, str_pass );
- mpr( str_pass );
-
- if (is_valid_item( you.inv[from_slot] ))
- {
- in_name( from_slot, DESC_INVENTORY_EQUIP, str_pass );
- mpr( str_pass );
- }
-
- if (to_slot == you.equip[EQ_WEAPON] || from_slot == you.equip[EQ_WEAPON])
- you.wield_change = true;
+ swap_inv_slots(from_slot, to_slot, true);
} // end adjust_item()
static void adjust_spells_cleanup(bool needs_redraw)
@@ -417,7 +455,7 @@ void list_armour(void)
strcat(info, " none");
}
- mpr( info, MSGCH_EQUIPMENT );
+ mpr( info, MSGCH_EQUIPMENT, menu_colour(info) );
}
} // end list_armour()
@@ -445,7 +483,7 @@ void list_jewellery(void)
strcat(info, " none");
}
- mpr( info, MSGCH_EQUIPMENT );
+ mpr( info, MSGCH_EQUIPMENT, menu_colour(info) );
}
} // end list_jewellery()
@@ -473,7 +511,7 @@ void list_weapons(void)
strcat(info, " empty hands");
}
- mpr(info);
+ mpr(info, MSGCH_EQUIPMENT, menu_colour(info));
// Print out the swap slots
for (int i = 0; i <= 1; i++)
@@ -494,7 +532,7 @@ void list_weapons(void)
else
strcat(info, " none");
- mpr(info); // Output slot
+ mpr(info, MSGCH_EQUIPMENT, menu_colour(info)); // Output slot
}
// Now we print out the current default fire weapon
@@ -511,5 +549,5 @@ void list_weapons(void)
strcat( info, str_pass );
}
- mpr( info, MSGCH_EQUIPMENT );
+ mpr( info, MSGCH_EQUIPMENT, menu_colour(info) );
} // end list_weapons()
diff --git a/trunk/source/command.h b/trunk/source/command.h
index 01fc719290..7bdd3a9e85 100644
--- a/trunk/source/command.h
+++ b/trunk/source/command.h
@@ -55,4 +55,6 @@ void list_armour(void);
void list_jewellery(void);
+void swap_inv_slots(int slot1, int slot2, bool verbose);
+
#endif
diff --git a/trunk/source/debug.cc b/trunk/source/debug.cc
index 265db7e1e1..8bfdb776d8 100644
--- a/trunk/source/debug.cc
+++ b/trunk/source/debug.cc
@@ -906,8 +906,15 @@ void create_spec_object(void)
if (type_wanted == -1)
{
- mpr( "No such item." );
- return;
+ // ds -- if specs is a valid int, try using that.
+ // Since zero is atoi's copout, the wizard
+ // must enter (subtype + 1).
+ if (!(type_wanted = atoi(specs)))
+ {
+ mpr( "No such item." );
+ return;
+ }
+ type_wanted--;
}
mitm[thing_created].sub_type = type_wanted;
@@ -986,8 +993,10 @@ void create_spec_object(void)
move_item_to_grid( &thing_created, you.x_pos, you.y_pos );
if (thing_created != NON_ITEM)
+ {
+ origin_acquired( mitm[thing_created], AQ_WIZMODE );
canned_msg( MSG_SOMETHING_APPEARS );
-
+ }
}
#endif
diff --git a/trunk/source/decks.cc b/trunk/source/decks.cc
index cac48d2cbb..5f958b0512 100644
--- a/trunk/source/decks.cc
+++ b/trunk/source/decks.cc
@@ -529,7 +529,7 @@ static void cards(unsigned char which_card)
case CARD_ACQUISITION:
mpr( "You have drawn Acquisition!" );
mpr( "The card unfolds to form a scroll of paper." );
- acquirement( OBJ_RANDOM );
+ acquirement( OBJ_RANDOM, AQ_CARD_ACQUISITION );
break;
case CARD_HASTEN:
@@ -858,17 +858,17 @@ static void cards(unsigned char which_card)
case CARD_VIOLENCE:
mpr("You have drawn Violence.");
- acquirement( OBJ_WEAPONS );
+ acquirement( OBJ_WEAPONS, AQ_CARD_VIOLENCE );
break;
case CARD_PROTECTION:
mpr("You have drawn Protection.");
- acquirement( OBJ_ARMOUR );
+ acquirement( OBJ_ARMOUR, AQ_CARD_PROTECTION );
break;
case CARD_KNOWLEDGE:
mpr("You have drawn Knowledge.");
- acquirement( OBJ_BOOKS );
+ acquirement( OBJ_BOOKS, AQ_CARD_KNOWLEDGE );
break;
case CARD_MAZE:
diff --git a/trunk/source/defines.h b/trunk/source/defines.h
index 65ee839714..9f61c2961b 100644
--- a/trunk/source/defines.h
+++ b/trunk/source/defines.h
@@ -84,6 +84,14 @@
#define mgrd env.mgrid
#define igrd env.igrid
+#define ENVF_FLAGS 0xFF00U
+#define ENVF_DETECT_MONS 0x0100U
+#define ENVF_DETECT_ITEM 0x0200U
+
+// This square is known because of detect-creatures/detect-items
+#define ENVF_DETECTED 0x0800U
+
+#define ENVF_COLOR(x) (((x) >> 12) & 0xF)
// (MNG) -- for a reason! see usage {dlb}:
#define MHITNOT 201
@@ -124,30 +132,30 @@
// try to get a brighter version using recommisioned attribute flags.
#define COLFLAG_CURSES_BRIGHTEN 0x0080
-#ifdef USE_COLOUR_OPTS
+//#ifdef USE_COLOUR_OPTS
#define COLFLAG_FRIENDLY_MONSTER 0x0100
+ #define COLFLAG_ITEM_HEAP 0x0200
+ #define COLFLAG_WILLSTAB 0x0400
+ #define COLFLAG_MAYSTAB 0x0800
enum CHAR_ATTRIBUTES
{
- CHATTR_NORMAL,
+ CHATTR_NORMAL, /* 0 */
CHATTR_STANDOUT,
CHATTR_BOLD,
CHATTR_BLINK,
CHATTR_UNDERLINE,
- CHATTR_REVERSE,
- CHATTR_DIM
- };
+ CHATTR_REVERSE, /* 5 */
+ CHATTR_DIM,
+ CHATTR_HILITE,
+
+ CHATTR_ATTRMASK = 0xF, /* 15 (well, obviously) */
-#endif
+ CHATTR_COLMASK = 0xF00, // Mask with this to get extra colour info.
+ };
-// required for stuff::coinflip()
-#define IB1 1
-#define IB2 2
-#define IB5 16
-#define IB18 131072
-#define MASK (IB1 + IB2 + IB5)
-// required for stuff::coinflip()
+//#endif
#define MINIMUM( xxx, yyy ) (((xxx) < (yyy)) ? (xxx) : (yyy))
#define MAXIMUM( xxx, yyy ) (((xxx) > (yyy)) ? (xxx) : (yyy))
diff --git a/trunk/source/describe.cc b/trunk/source/describe.cc
index a02e33b95d..173e9b0ed4 100644
--- a/trunk/source/describe.cc
+++ b/trunk/source/describe.cc
@@ -39,6 +39,7 @@
#include "randart.h"
#include "religion.h"
#include "skills2.h"
+#include "spl-book.h"
#include "stuff.h"
#include "wpn-misc.h"
#include "spl-util.h"
@@ -347,7 +348,8 @@ static std::string describe_demon(void)
globby *= strlen( ghost.name );
- srand( globby );
+ push_rng_state();
+ seed_rng( globby );
std::string description = "A powerful demon, ";
@@ -670,6 +672,7 @@ static std::string describe_demon(void)
break;
}
+ pop_rng_state();
return description;
} // end describe_demon()
@@ -3281,9 +3284,24 @@ void describe_item( const item_def &item )
clrscr();
std::string description = get_item_description( item, 1 );
-
print_description(description);
+ if ( (item.base_type == OBJ_BOOKS && item_type_known(item)
+ && item.sub_type != BOOK_DESTRUCTION
+ && item.sub_type != BOOK_MANUAL)
+ ||
+ count_staff_spells(item, true) > 1 )
+ {
+ formatted_string fs;
+ item_def dup = item;
+ spellbook_contents( dup,
+ item.base_type == OBJ_BOOKS?
+ RBOOK_READ_SPELL
+ : RBOOK_USE_STAFF,
+ &fs );
+ fs.display(2, -2);
+ }
+
if (getch() == 0)
getch();
@@ -5789,67 +5807,9 @@ void describe_monsters(int class_described, unsigned char which_mons)
break;
case MONS_PLAYER_GHOST:
- {
- char tmp_buff[ INFO_SIZE ];
-
- // We're fudgins stats so that unarmed combat gets based off
- // of the ghost's species, not the player's stats... exact
- // stats are required anyways, all that matters is whether
- // dex >= str. -- bwr
- const int dex = 10;
- int str;
- switch (ghost.values[GVAL_SPECIES])
- {
- case SP_HILL_DWARF:
- case SP_MOUNTAIN_DWARF:
- case SP_TROLL:
- case SP_OGRE:
- case SP_OGRE_MAGE:
- case SP_MINOTAUR:
- case SP_HILL_ORC:
- case SP_CENTAUR:
- case SP_NAGA:
- case SP_MUMMY:
- case SP_GHOUL:
- str = 15;
- break;
-
- case SP_HUMAN:
- case SP_DEMIGOD:
- case SP_DEMONSPAWN:
- str = 10;
- break;
-
- default:
- str = 5;
- break;
- }
-
- snprintf( tmp_buff, sizeof(tmp_buff),
- "The apparition of %s the %s, a%s %s %s.$",
- ghost.name,
-
- skill_title( ghost.values[GVAL_BEST_SKILL],
- ghost.values[GVAL_SKILL_LEVEL],
- ghost.values[GVAL_SPECIES],
- str, dex, GOD_NO_GOD ),
-
- (ghost.values[GVAL_EXP_LEVEL] < 4) ? " weakling" :
- (ghost.values[GVAL_EXP_LEVEL] < 7) ? "n average" :
- (ghost.values[GVAL_EXP_LEVEL] < 11) ? "n experienced" :
- (ghost.values[GVAL_EXP_LEVEL] < 16) ? " powerful" :
- (ghost.values[GVAL_EXP_LEVEL] < 22) ? " mighty" :
- (ghost.values[GVAL_EXP_LEVEL] < 26) ? " great" :
- (ghost.values[GVAL_EXP_LEVEL] < 27) ? "n awesomely powerful"
- : " legendary",
-
- species_name( ghost.values[GVAL_SPECIES],
- ghost.values[GVAL_EXP_LEVEL] ),
-
- get_class_name( ghost.values[GVAL_CLASS] ) );
-
- description += tmp_buff;
- }
+ description += "The apparition of ";
+ description += ghost_description();
+ description += ".$";
break;
case MONS_PANDEMONIUM_DEMON:
@@ -6168,6 +6128,79 @@ void describe_monsters(int class_described, unsigned char which_mons)
#endif
} // end describe_monsters
+//---------------------------------------------------------------
+//
+// ghost_description
+//
+// Describes the current ghost's previous owner. The caller must
+// prepend "The apparition of" or whatever and append any trailing
+// punctuation that's wanted.
+//
+//---------------------------------------------------------------
+std::string ghost_description(bool concise)
+{
+ char tmp_buff[ INFO_SIZE ];
+
+ // We're fudging stats so that unarmed combat gets based off
+ // of the ghost's species, not the player's stats... exact
+ // stats are required anyways, all that matters is whether
+ // dex >= str. -- bwr
+ const int dex = 10;
+ int str;
+ switch (ghost.values[GVAL_SPECIES])
+ {
+ case SP_HILL_DWARF:
+ case SP_MOUNTAIN_DWARF:
+ case SP_TROLL:
+ case SP_OGRE:
+ case SP_OGRE_MAGE:
+ case SP_MINOTAUR:
+ case SP_HILL_ORC:
+ case SP_CENTAUR:
+ case SP_NAGA:
+ case SP_MUMMY:
+ case SP_GHOUL:
+ str = 15;
+ break;
+
+ case SP_HUMAN:
+ case SP_DEMIGOD:
+ case SP_DEMONSPAWN:
+ str = 10;
+ break;
+
+ default:
+ str = 5;
+ break;
+ }
+
+ snprintf( tmp_buff, sizeof(tmp_buff),
+ "%s the %s, a%s %s %s",
+ ghost.name,
+
+ skill_title( ghost.values[GVAL_BEST_SKILL],
+ ghost.values[GVAL_SKILL_LEVEL],
+ ghost.values[GVAL_SPECIES],
+ str, dex, GOD_NO_GOD ),
+
+ (ghost.values[GVAL_EXP_LEVEL] < 4) ? " weakling" :
+ (ghost.values[GVAL_EXP_LEVEL] < 7) ? "n average" :
+ (ghost.values[GVAL_EXP_LEVEL] < 11) ? "n experienced" :
+ (ghost.values[GVAL_EXP_LEVEL] < 16) ? " powerful" :
+ (ghost.values[GVAL_EXP_LEVEL] < 22) ? " mighty" :
+ (ghost.values[GVAL_EXP_LEVEL] < 26) ? " great" :
+ (ghost.values[GVAL_EXP_LEVEL] < 27) ? "n awesomely powerful"
+ : " legendary",
+
+ ( concise? get_species_abbrev(ghost.values[GVAL_SPECIES]) :
+ species_name( ghost.values[GVAL_SPECIES],
+ ghost.values[GVAL_EXP_LEVEL] ) ),
+
+ ( concise? get_class_abbrev(ghost.values[GVAL_CLASS]) :
+ get_class_name( ghost.values[GVAL_CLASS] ) ) );
+
+ return std::string(tmp_buff);
+}
static void print_god_abil_desc( int abil )
{
diff --git a/trunk/source/describe.h b/trunk/source/describe.h
index 73037a7001..981f90ce01 100644
--- a/trunk/source/describe.h
+++ b/trunk/source/describe.h
@@ -55,4 +55,10 @@ void describe_monsters(int class_described, unsigned char which_mons);
* *********************************************************************** */
void describe_spell(int spelled);
+// last updated 13oct2003 {darshan}
+/* ***********************************************************************
+ * called from: describe_monsters - describe, kill_ghost - Kills
+ * *********************************************************************** */
+std::string ghost_description(bool concise = false);
+
#endif
diff --git a/trunk/source/direct.cc b/trunk/source/direct.cc
index 3284ce7413..8ff8a9732a 100644
--- a/trunk/source/direct.cc
+++ b/trunk/source/direct.cc
@@ -17,6 +17,7 @@
#include "AppHdr.h"
#include "direct.h"
+#include <cstdarg>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
@@ -36,24 +37,65 @@
#include "shopping.h"
#include "stuff.h"
#include "spells4.h"
+#include "stash.h"
+#include "travel.h"
#include "view.h"
#ifdef USE_MACROS
#include "macro.h"
#endif
+enum LOSSelect
+{
+ LOS_ANY = 0x00,
+
+ // Check only visible squares
+ LOS_VISIBLE = 0x01,
+
+ // Check only hidden squares
+ LOS_HIDDEN = 0x02,
+
+ LOS_VISMASK = 0x03,
+
+ // Flip from visible to hidden when going forward,
+ // or hidden to visible when going backwards.
+ LOS_FLIPVH = 0x20,
+
+ // Flip from hidden to visible when going forward,
+ // or visible to hidden when going backwards.
+ LOS_FLIPHV = 0x40,
+
+ LOS_NONE = 0xFFFF,
+};
+
// x and y offsets in the following order:
// SW, S, SE, W, E, NW, N, NE
static const char xcomp[9] = { -1, 0, 1, -1, 0, 1, -1, 0, 1 };
static const char ycomp[9] = { 1, 1, 1, 0, 0, 0, -1, -1, -1 };
-static const char dirchars[19] = { "b1j2n3h4.5l6y7k8u9" };
+
+// [dshaligram] Removed . and 5 from dirchars so it's easier to
+// special case them.
+static const char dirchars[19] = { "b1j2n3h4bbl6y7k8u9" };
static const char DOSidiocy[10] = { "OPQKSMGHI" };
static const char *aim_prompt = "Aim (move cursor or -/+/=, change mode with CTRL-F, select with . or >)";
+static void describe_feature(int mx, int my, bool oos);
static void describe_cell(int mx, int my);
-static char mons_find( unsigned char xps, unsigned char yps,
- FixedVector<char, 2> &mfp, char direction,
- int mode = TARG_ANY );
+
+static bool find_object( int x, int y, int mode );
+static bool find_monster( int x, int y, int mode );
+static bool find_feature( int x, int y, int mode );
+static char find_square( unsigned char xps, unsigned char yps,
+ FixedVector<char, 2> &mfp, char direction,
+ bool (*targ)(int, int, int),
+ int mode = TARG_ANY,
+ bool wrap = false,
+ int los = LOS_ANY);
+
+static bool is_mapped(int x, int y)
+{
+ return (is_player_mapped(x, y));
+}
//---------------------------------------------------------------
//
@@ -103,11 +145,11 @@ void direction( struct dist &moves, int restrict, int mode )
// nonetheless! --GDL
gotoxy( 18, 9 );
- int keyin = getch();
+ int keyin = getchm(KC_TARGETING);
if (keyin == 0) // DOS idiocy (emulated by win32 console)
{
- keyin = getch(); // grrr.
+ keyin = getchm(KC_TARGETING); // grrr.
if (keyin == '*')
{
targChosen = true;
@@ -156,6 +198,16 @@ void direction( struct dist &moves, int restrict, int mode )
dir = 0;
break;
+ case ';':
+ targChosen = true;
+ dir = -3;
+ break;
+
+ case '\'':
+ targChosen = true;
+ dir = -2;
+ break;
+
case '+':
case '=':
targChosen = true;
@@ -168,6 +220,12 @@ void direction( struct dist &moves, int restrict, int mode )
dir = 2;
break;
+ case '.':
+ case '5':
+ dirChosen = true;
+ dir = 4;
+ break;
+
case ESCAPE:
moves.isCancel = true;
return;
@@ -262,6 +320,21 @@ void direction( struct dist &moves, int restrict, int mode )
moves.ty = you.y_pos + moves.dy * my;
}
+// Attempts to describe a square that's not in line-of-sight. If
+// there's a stash on the square, announces the top item and number
+// of items, otherwise, if there's a stair that's in the travel
+// cache and noted in the Dungeon (O)verview, names the stair.
+static void describe_oos_square(int x, int y)
+{
+ if (!is_mapped(x, y))
+ return;
+
+#ifdef STASH_TRACKING
+ describe_stash(x, y);
+#endif
+ describe_feature(x, y, true);
+}
+
//---------------------------------------------------------------
//
// look_around
@@ -298,9 +371,10 @@ void look_around(struct dist &moves, bool justLooking, int first_move, int mode)
int mx, my; // actual map x,y (scratch)
int mid; // monster id (scratch)
FixedVector < char, 2 > monsfind_pos;
+ FixedVector < char, 2 > objfind_pos;
- monsfind_pos[0] = you.x_pos;
- monsfind_pos[1] = you.y_pos;
+ monsfind_pos[0] = objfind_pos[0] = you.x_pos;
+ monsfind_pos[1] = objfind_pos[1] = you.y_pos;
message_current_target();
@@ -311,6 +385,10 @@ void look_around(struct dist &moves, bool justLooking, int first_move, int mode)
keyin = '-';
if (moves.prev_target == 1)
keyin = '+';
+ if (moves.prev_target == -2)
+ keyin = '\'';
+ if (moves.prev_target == -3)
+ keyin = ';';
// reset
moves.prev_target = 0;
@@ -326,13 +404,21 @@ void look_around(struct dist &moves, bool justLooking, int first_move, int mode)
gotoxy(cx+1, cy);
if (keyin == 0)
- keyin = getch();
+ keyin = getchm(KC_TARGETING);
+
+ // [dshaligram] Classic Crawl behaviour was to use space to select
+ // targets when targeting. The patch changed the meaning of space
+ // from 'confirm' to 'cancel', which surprised some folks. I'm now
+ // arbitrarily defining space as 'cancel' for look-around, and
+ // 'confirm' for targeting.
+ if (!justLooking && keyin == ' ')
+ keyin = '\r';
// DOS idiocy
if (keyin == 0)
{
// get the extended key
- keyin = getch();
+ keyin = getchm(KC_TARGETING);
// look for CR - change to '5' to indicate selection
if (keyin == 13)
@@ -367,28 +453,64 @@ void look_around(struct dist &moves, bool justLooking, int first_move, int mode)
targChosen = true;
break;
- case '-':
- if (mons_find( cx, cy, monsfind_pos, -1, mode ) == 0)
- flush_input_buffer( FLUSH_ON_FAILURE );
- else
+ case '^':
+ case '\t':
+ case '\\':
+ case '_':
+ case '<':
+ case '>':
+ {
+ if (find_square( cx, cy, objfind_pos, 1,
+ find_feature, keyin, true,
+ Options.target_los_first
+ ? LOS_FLIPVH : LOS_ANY))
{
- newcx = monsfind_pos[0];
- newcy = monsfind_pos[1];
+ newcx = objfind_pos[0];
+ newcy = objfind_pos[1];
}
+ else
+ flush_input_buffer( FLUSH_ON_FAILURE );
targChosen = true;
break;
+ }
+ case ';':
+ case '/':
+ case '\'':
+ case '*':
+ {
+ int dir = keyin == ';' || keyin == '/'? -1 : 1;
+ if (find_square( cx, cy, objfind_pos, dir,
+ find_object, 0, true,
+ Options.target_los_first
+ ? (dir == 1? LOS_FLIPVH : LOS_FLIPHV)
+ : LOS_ANY))
+
+ {
+ newcx = objfind_pos[0];
+ newcy = objfind_pos[1];
+ }
+ else
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ targChosen = true;
+ break;
+ }
+ case '-':
case '+':
case '=':
- if (mons_find( cx, cy, monsfind_pos, 1, mode ) == 0)
- flush_input_buffer( FLUSH_ON_FAILURE );
- else
{
- newcx = monsfind_pos[0];
- newcy = monsfind_pos[1];
+ int dir = keyin == '-'? -1 : 1;
+ if (find_square( cx, cy, monsfind_pos, dir,
+ find_monster, mode, Options.target_wrap ))
+ {
+ newcx = monsfind_pos[0];
+ newcy = monsfind_pos[1];
+ }
+ else
+ flush_input_buffer( FLUSH_ON_FAILURE );
+ targChosen = true;
+ break;
}
- targChosen = true;
- break;
case 't':
case 'p':
@@ -418,13 +540,33 @@ void look_around(struct dist &moves, bool justLooking, int first_move, int mode)
case '\r':
case '\n':
- case '>':
- case ' ':
case '.':
- dirChosen = true;
- dir = 4;
+ case '5':
+ // If we're in look-around mode, and the cursor is on
+ // the character and there's a valid travel target
+ // within the viewport, jump to that target.
+ if (justLooking && cx == 17 && cy == 9)
+ {
+ if (you.travel_x > 0 && you.travel_y > 0)
+ {
+ int nx = you.travel_x - you.x_pos + 17;
+ int ny = you.travel_y - you.y_pos + 9;
+ if (in_viewport_bounds(nx, ny))
+ {
+ newcx = nx;
+ newcy = ny;
+ targChosen = true;
+ }
+ }
+ }
+ else
+ {
+ dirChosen = true;
+ dir = 4;
+ }
break;
+ case ' ':
case ESCAPE:
moves.isCancel = true;
mesclr( true );
@@ -444,12 +586,18 @@ void look_around(struct dist &moves, bool justLooking, int first_move, int mode)
// check for SELECTION
if (dirChosen && dir == 4)
{
- // RULE: cannot target what you cannot see
- if (env.show[cx - 8][cy] == 0 && !(cx == 17 && cy == 9))
+ // [dshaligram] We no longer vet the square coordinates if
+ // we're justLooking. By not vetting the coordinates, we make 'x'
+ // look_around() nicer for travel purposes.
+ if (!justLooking)
{
- if (!justLooking)
+ // RULE: cannot target what you cannot see
+ if (!in_vlos(cx, cy))
+ {
+ // if (!justLooking)
mpr("Sorry, you can't target what you can't see.");
- return;
+ return;
+ }
}
moves.isValid = true;
@@ -485,8 +633,8 @@ void look_around(struct dist &moves, bool justLooking, int first_move, int mode)
}
// bounds check for newcx, newcy
- if (newcx < 9) newcx = 9;
- if (newcx > 25) newcx = 25;
+ if (newcx < 1) newcx = 1;
+ if (newcx > 33) newcx = 33;
if (newcy < 1) newcy = 1;
if (newcy > 17) newcy = 17;
@@ -498,9 +646,11 @@ void look_around(struct dist &moves, bool justLooking, int first_move, int mode)
cx = newcx;
cy = newcy;
mesclr( true );
- if (env.show[cx - 8][cy] == 0 && !(cx == 17 && cy == 9))
+ if (!in_vlos(cx, cy))
{
mpr("You can't see that place.");
+ describe_oos_square(you.x_pos + cx - 17,
+ you.y_pos + cy - 9);
continue;
}
describe_cell(you.x_pos + cx - 17, you.y_pos + cy - 9);
@@ -509,61 +659,220 @@ void look_around(struct dist &moves, bool justLooking, int first_move, int mode)
mesclr( true );
} // end look_around()
+bool in_vlos(int x, int y)
+{
+ return in_los_bounds(x, y) && (env.show[x - 8][y] || (x == 17 && y == 9));
+}
+
+bool in_los(int x, int y)
+{
+ const int tx = x + 9 - you.x_pos,
+ ty = y + 9 - you.y_pos;
+
+ if (!in_los_bounds(tx + 8, ty))
+ return (false);
+
+ return (x == you.x_pos && y == you.y_pos) || env.show[tx][ty];
+}
+
+static bool find_monster( int x, int y, int mode )
+{
+ const int targ_mon = mgrd[ x ][ y ];
+ return (targ_mon != NON_MONSTER
+ && in_los(x, y)
+ && player_monster_visible( &(menv[targ_mon]) )
+ && !mons_is_mimic( menv[targ_mon].type )
+ && (mode == TARG_ANY
+ || (mode == TARG_FRIEND && mons_friendly( &menv[targ_mon] ))
+ || (mode == TARG_ENEMY
+ && !mons_friendly( &menv[targ_mon] )
+ &&
+ (Options.target_zero_exp ||
+ !mons_flag( menv[targ_mon].type, M_NO_EXP_GAIN )) )));
+}
+
+static bool find_feature( int x, int y, int mode )
+{
+ // The stair need not be in LOS if the square is mapped.
+ if (!in_los(x, y) && (!Options.target_oos || !is_mapped(x, y)))
+ return (false);
+
+ return is_feature(mode, x, y);
+}
+
+static bool find_object(int x, int y, int mode)
+{
+ const int item = igrd[x][y];
+ // The square need not be in LOS if the stash tracker knows this item.
+ return (item != NON_ITEM
+ && (in_los(x, y)
+#ifdef STASH_TRACKING
+ || (Options.target_oos && is_mapped(x, y) && is_stash(x, y))
+#endif
+ ));
+}
+
+static int next_los(int dir, int los, bool wrap)
+{
+ if (los == LOS_ANY)
+ return (wrap? los : LOS_NONE);
+
+ bool vis = los & LOS_VISIBLE;
+ bool hidden = los & LOS_HIDDEN;
+ bool flipvh = los & LOS_FLIPVH;
+ bool fliphv = los & LOS_FLIPHV;
+
+ if (!vis && !hidden)
+ vis = true;
+
+ if (wrap)
+ {
+ if (!flipvh && !fliphv)
+ return (los);
+
+ // We have to invert flipvh and fliphv if we're wrapping. Here's
+ // why:
+ //
+ // * Say the cursor is on the last item in LOS, there are no
+ // items outside LOS, and wrap == true. flipvh is true.
+ // * We set wrap false and flip from visible to hidden, but there
+ // are no hidden items. So now we need to flip back to visible
+ // so we can go back to the first item in LOS. Unless we set
+ // fliphv, we can't flip from hidden to visible.
+ //
+ los = flipvh? LOS_FLIPHV : LOS_FLIPVH;
+ }
+ else
+ {
+ if (!flipvh && !fliphv)
+ return (LOS_NONE);
+
+ if (flipvh && vis != (dir == 1))
+ return (LOS_NONE);
+
+ if (fliphv && vis == (dir == 1))
+ return (LOS_NONE);
+ }
+
+ los = (los & ~LOS_VISMASK) | (vis? LOS_HIDDEN : LOS_VISIBLE);
+ return (los);
+}
+
+bool in_viewport_bounds(int x, int y)
+{
+ return (x >= 1 && x <= 33 && y >= 1 && y <= 17);
+}
+
+bool in_los_bounds(int x, int y)
+{
+ return !(x > 25 || x < 8 || y > 17 || y < 1);
+}
+
//---------------------------------------------------------------
//
-// mons_find
+// find_square
//
-// Finds the next monster (moving in a spiral outwards from the
-// player, so closer monsters are chosen first; starts to player's
-// left) and puts its coordinates in mfp. Returns 1 if it found
-// a monster, zero otherwise. If direction is -1, goes backwards.
+// Finds the next monster/object/whatever (moving in a spiral
+// outwards from the player, so closer targets are chosen first;
+// starts to player's left) and puts its coordinates in mfp.
+// Returns 1 if it found something, zero otherwise. If direction
+// is -1, goes backwards.
+//
+// If the game option target_zero_exp is true, zero experience
+// monsters will be targeted.
//
//---------------------------------------------------------------
-static char mons_find( unsigned char xps, unsigned char yps,
- FixedVector<char, 2> &mfp, char direction, int mode )
+static char find_square( unsigned char xps, unsigned char yps,
+ FixedVector<char, 2> &mfp, char direction,
+ bool (*find_targ)( int x, int y, int mode ),
+ int mode, bool wrap, int los )
{
- unsigned char temp_xps = xps;
- unsigned char temp_yps = yps;
+ int temp_xps = xps;
+ int temp_yps = yps;
char x_change = 0;
char y_change = 0;
+ bool onlyVis = false, onlyHidden = false;
+
int i, j;
- if (direction == 1 && temp_xps == 9 && temp_yps == 17)
- return (0); // end of spiral
+ if (los == LOS_NONE)
+ return (0);
+
+ if (los == LOS_FLIPVH || los == LOS_FLIPHV)
+ {
+ if (in_los_bounds(xps, yps))
+ {
+ // We've been told to flip between visible/hidden, so we
+ // need to find what we're currently on.
+ bool vis = (env.show[xps - 8][yps] || (xps == 17 && yps == 9));
+
+ if (wrap && (vis != (los == LOS_FLIPVH)) == (direction == 1))
+ {
+ // We've already flipped over into the other direction,
+ // so correct the flip direction if we're wrapping.
+ los = los == LOS_FLIPHV? LOS_FLIPVH : LOS_FLIPHV;
+ }
+
+ los = (los & ~LOS_VISMASK) | (vis? LOS_VISIBLE : LOS_HIDDEN);
+ }
+ else
+ {
+ if (wrap)
+ los = LOS_HIDDEN | (direction == 1? LOS_FLIPHV : LOS_FLIPVH);
+ else
+ los |= LOS_HIDDEN;
+ }
+ }
+
+ onlyVis = (los & LOS_VISIBLE);
+ onlyHidden = (los & LOS_HIDDEN);
- while (temp_xps >= 8 && temp_xps <= 25 && temp_yps <= 17) // yps always >= 0
+ const int minx = 1, maxx = 33,
+ miny = -7, maxy = 25,
+ ctrx = 17, ctry = 9;
+
+ while (temp_xps >= minx - 1 && temp_xps <= maxx
+ && temp_yps <= maxy && temp_yps >= miny - 1)
{
- if (direction == -1 && temp_xps == 17 && temp_yps == 9)
- return (0); // can't go backwards from you
+ if (direction == 1 && temp_xps == minx && temp_yps == maxy)
+ {
+ return find_square(ctrx, ctry, mfp, direction, find_targ, mode,
+ false, next_los(direction, los, wrap));
+ }
+ if (direction == -1 && temp_xps == ctrx && temp_yps == ctry)
+ {
+ return find_square(minx, maxy, mfp, direction, find_targ, mode,
+ false, next_los(direction, los, wrap));
+ }
if (direction == 1)
{
- if (temp_xps == 8)
+ if (temp_xps == minx - 1)
{
x_change = 0;
y_change = -1;
}
- else if (temp_xps - 17 == 0 && temp_yps - 9 == 0)
+ else if (temp_xps == ctrx && temp_yps == ctry)
{
x_change = -1;
y_change = 0;
}
- else if (abs(temp_xps - 17) <= abs(temp_yps - 9))
+ else if (abs(temp_xps - ctrx) <= abs(temp_yps - ctry))
{
- if (temp_xps - 17 >= 0 && temp_yps - 9 <= 0)
+ if (temp_xps - ctrx >= 0 && temp_yps - ctry <= 0)
{
- if (abs(temp_xps - 17) > abs(temp_yps - 9 + 1))
+ if (abs(temp_xps - ctrx) > abs(temp_yps - ctry + 1))
{
x_change = 0;
y_change = -1;
- if (temp_xps - 17 > 0)
+ if (temp_xps - ctrx > 0)
y_change = 1;
goto finished_spiralling;
}
}
x_change = -1;
- if (temp_yps - 9 < 0)
+ if (temp_yps - ctry < 0)
x_change = 1;
y_change = 0;
}
@@ -571,7 +880,7 @@ static char mons_find( unsigned char xps, unsigned char yps,
{
x_change = 0;
y_change = -1;
- if (temp_xps - 17 > 0)
+ if (temp_xps - ctrx > 0)
y_change = 1;
}
} // end if (direction == 1)
@@ -588,20 +897,20 @@ static char mons_find( unsigned char xps, unsigned char yps,
if (i == 0 && j == 0)
continue;
- if (temp_xps + i == 8)
+ if (temp_xps + i == minx - 1)
{
x_change = 0;
y_change = -1;
}
- else if (temp_xps + i - 17 == 0 && temp_yps + j - 9 == 0)
+ else if (temp_xps + i - ctrx == 0 && temp_yps + j - ctry == 0)
{
x_change = -1;
y_change = 0;
}
- else if (abs(temp_xps + i - 17) <= abs(temp_yps + j - 9))
+ else if (abs(temp_xps + i - ctrx) <= abs(temp_yps + j - ctry))
{
- const int xi = temp_xps + i - 17;
- const int yj = temp_yps + j - 9;
+ const int xi = temp_xps + i - ctrx;
+ const int yj = temp_yps + j - ctry;
if (xi >= 0 && yj <= 0)
{
@@ -624,7 +933,7 @@ static char mons_find( unsigned char xps, unsigned char yps,
{
x_change = 0;
y_change = -1;
- if (temp_xps + i - 17 > 0)
+ if (temp_xps + i - ctrx > 0)
y_change = 1;
}
@@ -643,43 +952,318 @@ static char mons_find( unsigned char xps, unsigned char yps,
y_change *= direction;
temp_xps += x_change;
- if (temp_yps + y_change <= 17) // it can wrap, unfortunately
+ if (temp_yps + y_change <= maxy) // it can wrap, unfortunately
temp_yps += y_change;
- const int targ_x = you.x_pos + temp_xps - 17;
- const int targ_y = you.y_pos + temp_yps - 9;
+ const int targ_x = you.x_pos + temp_xps - ctrx;
+ const int targ_y = you.y_pos + temp_yps - ctry;
// We don't want to be looking outside the bounds of the arrays:
- if (temp_xps > 25 || temp_xps < 8 || temp_yps > 17 || temp_yps < 1)
+ //if (!in_los_bounds(temp_xps, temp_yps))
+ // continue;
+
+ if (temp_xps < minx - 1 || temp_xps > maxx
+ || temp_yps < 1 || temp_yps > 17)
continue;
- if (targ_x < 0 || targ_x >= GXM || targ_y < 0 || targ_y >= GYM)
+ if (targ_x < 1 || targ_x >= GXM || targ_y < 1 || targ_y >= GYM)
continue;
- const int targ_mon = mgrd[ targ_x ][ targ_y ];
+ if ((onlyVis || onlyHidden) && onlyVis != in_los(targ_x, targ_y))
+ continue;
- if (targ_mon != NON_MONSTER
- && env.show[temp_xps - 8][temp_yps] != 0
- && player_monster_visible( &(menv[targ_mon]) )
- && !mons_is_mimic( menv[targ_mon].type )
- && (mode == TARG_ANY
- || (mode == TARG_FRIEND && mons_friendly( &menv[targ_mon] ))
- || (mode == TARG_ENEMY && !mons_friendly( &menv[targ_mon] ))))
- {
- //mpr("Found something!");
- //more();
+ if (find_targ(targ_x, targ_y, mode)) {
mfp[0] = temp_xps;
mfp[1] = temp_yps;
return (1);
}
}
- return (0);
+ return (direction == 1?
+ find_square(ctrx, ctry, mfp, direction, find_targ, mode, false,
+ next_los(direction, los, wrap))
+ : find_square(minx, maxy, mfp, direction, find_targ, mode, false,
+ next_los(direction, los, wrap)));
}
-static void describe_cell(int mx, int my)
+static bool is_shopstair(int x, int y)
+{
+ return (is_stair(grd[x][y]) || grd[x][y] == DNGN_ENTER_SHOP);
+}
+
+extern unsigned char (*mapch2) (unsigned char);
+static bool is_full_mapped(int x, int y)
+{
+ unsigned grid = grd[x][y];
+ int envch = env.map[x - 1][y - 1];
+ return (envch && envch == mapch2(grid));
+}
+
+static int surround_nonshopstair_count(int x, int y)
+{
+ int count = 0;
+ for (int ix = -1; ix < 2; ++ix)
+ {
+ for (int iy = -1; iy < 2; ++iy)
+ {
+ int nx = x + ix, ny = y + iy;
+ if (nx <= 0 || nx >= GXM || ny <= 0 || ny >= GYM)
+ continue;
+ if (is_full_mapped(nx, ny) && !is_shopstair(nx, ny))
+ count++;
+ }
+ }
+ return (count);
+}
+
+// For want of a better name...
+static bool clear_mapped(int x, int y)
+{
+ if (!is_full_mapped(x, y))
+ return (false);
+
+ if (is_shopstair(x, y))
+ return (surround_nonshopstair_count(x, y) > 0);
+
+ return (true);
+}
+
+static void describe_feature(int mx, int my, bool oos)
+{
+ if (oos && !clear_mapped(mx, my))
+ return;
+
+ unsigned oldfeat = grd[mx][my];
+ if (oos && env.map[mx - 1][my - 1] == mapch2(DNGN_SECRET_DOOR))
+ grd[mx][my] = DNGN_ROCK_WALL;
+
+ std::string desc = feature_description(mx, my);
+
+ grd[mx][my] = oldfeat;
+
+ if (desc.length())
+ {
+ if (oos)
+ desc = "[" + desc + "]";
+ mpr(desc.c_str());
+ }
+}
+
+std::string feature_description(int mx, int my)
{
int trf; // used for trap type??
+ switch (grd[mx][my])
+ {
+ case DNGN_STONE_WALL:
+ return ("A stone wall.");
+ case DNGN_ROCK_WALL:
+ case DNGN_SECRET_DOOR:
+ if (you.level_type == LEVEL_PANDEMONIUM)
+ return ("A wall of the weird stuff which makes up Pandemonium.");
+ else
+ return ("A rock wall.");
+ case DNGN_PERMAROCK_WALL:
+ return ("An unnaturally hard rock wall.");
+ case DNGN_CLOSED_DOOR:
+ return ("A closed door.");
+ case DNGN_METAL_WALL:
+ return ("A metal wall.");
+ case DNGN_GREEN_CRYSTAL_WALL:
+ return ("A wall of green crystal.");
+ case DNGN_ORCISH_IDOL:
+ return ("An orcish idol.");
+ case DNGN_WAX_WALL:
+ return ("A wall of solid wax.");
+ case DNGN_SILVER_STATUE:
+ return ("A silver statue.");
+ case DNGN_GRANITE_STATUE:
+ return ("A granite statue.");
+ case DNGN_ORANGE_CRYSTAL_STATUE:
+ return ("An orange crystal statue.");
+ case DNGN_LAVA:
+ return ("Some lava.");
+ case DNGN_DEEP_WATER:
+ return ("Some deep water.");
+ case DNGN_SHALLOW_WATER:
+ return ("Some shallow water.");
+ case DNGN_UNDISCOVERED_TRAP:
+ case DNGN_FLOOR:
+ return ("Floor.");
+ case DNGN_OPEN_DOOR:
+ return ("An open door.");
+ case DNGN_ROCK_STAIRS_DOWN:
+ return ("A rock staircase leading down.");
+ case DNGN_STONE_STAIRS_DOWN_I:
+ case DNGN_STONE_STAIRS_DOWN_II:
+ case DNGN_STONE_STAIRS_DOWN_III:
+ return ("A stone staircase leading down.");
+ case DNGN_ROCK_STAIRS_UP:
+ return ("A rock staircase leading upwards.");
+ case DNGN_STONE_STAIRS_UP_I:
+ case DNGN_STONE_STAIRS_UP_II:
+ case DNGN_STONE_STAIRS_UP_III:
+ return ("A stone staircase leading up.");
+ case DNGN_ENTER_HELL:
+ return ("A gateway to hell.");
+ case DNGN_BRANCH_STAIRS:
+ return ("A staircase to a branch level.");
+ case DNGN_TRAP_MECHANICAL:
+ case DNGN_TRAP_MAGICAL:
+ case DNGN_TRAP_III:
+ for (trf = 0; trf < MAX_TRAPS; trf++)
+ {
+ if (env.trap[trf].x == mx
+ && env.trap[trf].y == my)
+ {
+ break;
+ }
+
+ if (trf == MAX_TRAPS - 1)
+ {
+ mpr("Error - couldn't find that trap.");
+ error_message_to_player();
+ break;
+ }
+ }
+
+ switch (env.trap[trf].type)
+ {
+ case TRAP_DART:
+ return ("A dart trap.");
+ case TRAP_ARROW:
+ return ("An arrow trap.");
+ case TRAP_SPEAR:
+ return ("A spear trap.");
+ case TRAP_AXE:
+ return ("An axe trap.");
+ case TRAP_TELEPORT:
+ return ("A teleportation trap.");
+ case TRAP_AMNESIA:
+ return ("An amnesia trap.");
+ case TRAP_BLADE:
+ return ("A blade trap.");
+ case TRAP_BOLT:
+ return ("A bolt trap.");
+ case TRAP_ZOT:
+ return ("A Zot trap.");
+ case TRAP_NEEDLE:
+ return ("A needle trap.");
+ default:
+ mpr("An undefined trap. Huh?");
+ error_message_to_player();
+ break;
+ }
+ break;
+ case DNGN_ENTER_SHOP:
+ return (shop_name(mx, my));
+ case DNGN_ENTER_LABYRINTH:
+ return ("A labyrinth entrance.");
+ case DNGN_ENTER_DIS:
+ return ("A gateway to the Iron City of Dis.");
+ case DNGN_ENTER_GEHENNA:
+ return ("A gateway to Gehenna.");
+ case DNGN_ENTER_COCYTUS:
+ return ("A gateway to the freezing wastes of Cocytus.");
+ case DNGN_ENTER_TARTARUS:
+ return ("A gateway to the decaying netherworld of Tartarus.");
+ case DNGN_ENTER_ABYSS:
+ return ("A gateway to the infinite Abyss.");
+ case DNGN_EXIT_ABYSS:
+ return ("A gateway leading out of the Abyss.");
+ case DNGN_STONE_ARCH:
+ return ("An empty arch of ancient stone.");
+ case DNGN_ENTER_PANDEMONIUM:
+ return ("A gate leading to the halls of Pandemonium.");
+ case DNGN_EXIT_PANDEMONIUM:
+ return ("A gate leading out of Pandemonium.");
+ case DNGN_TRANSIT_PANDEMONIUM:
+ return ("A gate leading to another region of Pandemonium.");
+ case DNGN_ENTER_ORCISH_MINES:
+ return ("A staircase to the Orcish Mines.");
+ case DNGN_ENTER_HIVE:
+ return ("A staircase to the Hive.");
+ case DNGN_ENTER_LAIR:
+ return ("A staircase to the Lair.");
+ case DNGN_ENTER_SLIME_PITS:
+ return ("A staircase to the Slime Pits.");
+ case DNGN_ENTER_VAULTS:
+ return ("A staircase to the Vaults.");
+ case DNGN_ENTER_CRYPT:
+ return ("A staircase to the Crypt.");
+ case DNGN_ENTER_HALL_OF_BLADES:
+ return ("A staircase to the Hall of Blades.");
+ case DNGN_ENTER_ZOT:
+ return ("A gate to the Realm of Zot.");
+ case DNGN_ENTER_TEMPLE:
+ return ("A staircase to the Ecumenical Temple.");
+ case DNGN_ENTER_SNAKE_PIT:
+ return ("A staircase to the Snake Pit.");
+ case DNGN_ENTER_ELVEN_HALLS:
+ return ("A staircase to the Elven Halls.");
+ case DNGN_ENTER_TOMB:
+ return ("A staircase to the Tomb.");
+ case DNGN_ENTER_SWAMP:
+ return ("A staircase to the Swamp.");
+ case DNGN_RETURN_FROM_ORCISH_MINES:
+ case DNGN_RETURN_FROM_HIVE:
+ case DNGN_RETURN_FROM_LAIR:
+ case DNGN_RETURN_FROM_VAULTS:
+ case DNGN_RETURN_FROM_TEMPLE:
+ return ("A staircase back to the Dungeon.");
+ case DNGN_RETURN_FROM_SLIME_PITS:
+ case DNGN_RETURN_FROM_SNAKE_PIT:
+ case DNGN_RETURN_FROM_SWAMP:
+ return ("A staircase back to the Lair.");
+ case DNGN_RETURN_FROM_CRYPT:
+ case DNGN_RETURN_FROM_HALL_OF_BLADES:
+ return ("A staircase back to the Vaults.");
+ case DNGN_RETURN_FROM_ELVEN_HALLS:
+ return ("A staircase back to the Mines.");
+ case DNGN_RETURN_FROM_TOMB:
+ return ("A staircase back to the Crypt.");
+ case DNGN_RETURN_FROM_ZOT:
+ return ("A gate leading back out of this place.");
+ case DNGN_ALTAR_ZIN:
+ return ("A glowing white marble altar of Zin.");
+ case DNGN_ALTAR_SHINING_ONE:
+ return ("A glowing golden altar of the Shining One.");
+ case DNGN_ALTAR_KIKUBAAQUDGHA:
+ return ("An ancient bone altar of Kikubaaqudgha.");
+ case DNGN_ALTAR_YREDELEMNUL:
+ return ("A basalt altar of Yredelemnul.");
+ case DNGN_ALTAR_XOM:
+ return ("A shimmering altar of Xom.");
+ case DNGN_ALTAR_VEHUMET:
+ return ("A shining altar of Vehumet.");
+ case DNGN_ALTAR_OKAWARU:
+ return ("An iron altar of Okawaru.");
+ case DNGN_ALTAR_MAKHLEB:
+ return ("A burning altar of Makhleb.");
+ case DNGN_ALTAR_SIF_MUNA:
+ return ("A deep blue altar of Sif Muna.");
+ case DNGN_ALTAR_TROG:
+ return ("A bloodstained altar of Trog.");
+ case DNGN_ALTAR_NEMELEX_XOBEH:
+ return ("A sparkling altar of Nemelex Xobeh.");
+ case DNGN_ALTAR_ELYVILON:
+ return ("A silver altar of Elyvilon.");
+ case DNGN_BLUE_FOUNTAIN:
+ return ("A fountain of clear blue water.");
+ case DNGN_SPARKLING_FOUNTAIN:
+ return ("A fountain of sparkling water.");
+ case DNGN_DRY_FOUNTAIN_I:
+ case DNGN_DRY_FOUNTAIN_II:
+ case DNGN_DRY_FOUNTAIN_IV:
+ case DNGN_DRY_FOUNTAIN_VI:
+ case DNGN_DRY_FOUNTAIN_VIII:
+ case DNGN_PERMADRY_FOUNTAIN:
+ return ("A dry fountain.");
+ }
+ return ("");
+}
+
+static void describe_cell(int mx, int my)
+{
char str_pass[ ITEMNAME_SIZE ];
bool mimic_item = false;
@@ -738,7 +1322,7 @@ static void describe_cell(int mx, int my)
if (mon_arm != NON_ITEM)
{
- it_name( mon_arm, DESC_PLAIN, str_pass );
+ it_name( mon_arm, DESC_NOCAP_A, str_pass );
snprintf( info, INFO_SIZE, "%s is wearing %s.",
mons_pronoun( menv[i].type, PRONOUN_CAP ),
str_pass );
@@ -749,7 +1333,8 @@ static void describe_cell(int mx, int my)
if (menv[i].type == MONS_HYDRA)
{
- snprintf( info, INFO_SIZE, "It has %d heads.", menv[i].number );
+ snprintf( info, INFO_SIZE, "It has %d head%s.",
+ menv[i].number, (menv[i].number > 1? "s" : "") );
mpr( info );
}
@@ -766,9 +1351,15 @@ static void describe_cell(int mx, int my)
strcat(info, " doesn't appear to have noticed you.");
mpr(info);
}
- // wandering hostile with no target in LOS
- else if (menv[i].behaviour == BEH_WANDER && !mons_friendly(&menv[i])
- && menv[i].foe == MHITNOT)
+ // Applies to both friendlies and hostiles
+ else if (menv[i].behaviour == BEH_FLEE)
+ {
+ strcpy(info, mons_pronoun(menv[i].type, PRONOUN_CAP));
+ strcat(info, " is fleeing in terror.");
+ mpr(info);
+ }
+ // hostile with target != you
+ else if (!mons_friendly(&menv[i]) && menv[i].foe != MHITYOU)
{
// special case: batty monsters get set to BEH_WANDER as
// part of their special behaviour.
@@ -907,289 +1498,6 @@ static void describe_cell(int mx, int my)
}
}
- switch (grd[mx][my])
- {
- case DNGN_STONE_WALL:
- mpr("A stone wall.");
- break;
- case DNGN_ROCK_WALL:
- case DNGN_SECRET_DOOR:
- if (you.level_type == LEVEL_PANDEMONIUM)
- mpr("A wall of the weird stuff which makes up Pandemonium.");
- else
- mpr("A rock wall.");
- break;
- case DNGN_PERMAROCK_WALL:
- mpr("An unnaturally hard rock wall.");
- break;
- case DNGN_CLOSED_DOOR:
- mpr("A closed door.");
- break;
- case DNGN_METAL_WALL:
- mpr("A metal wall.");
- break;
- case DNGN_GREEN_CRYSTAL_WALL:
- mpr("A wall of green crystal.");
- break;
- case DNGN_ORCISH_IDOL:
- mpr("An orcish idol.");
- break;
- case DNGN_WAX_WALL:
- mpr("A wall of solid wax.");
- break;
- case DNGN_SILVER_STATUE:
- mpr("A silver statue.");
- break;
- case DNGN_GRANITE_STATUE:
- mpr("A granite statue.");
- break;
- case DNGN_ORANGE_CRYSTAL_STATUE:
- mpr("An orange crystal statue.");
- break;
- case DNGN_LAVA:
- mpr("Some lava.");
- break;
- case DNGN_DEEP_WATER:
- mpr("Some deep water.");
- break;
- case DNGN_SHALLOW_WATER:
- mpr("Some shallow water.");
- break;
- case DNGN_UNDISCOVERED_TRAP:
- case DNGN_FLOOR:
- mpr("Floor.");
- break;
- case DNGN_OPEN_DOOR:
- mpr("An open door.");
- break;
- case DNGN_ROCK_STAIRS_DOWN:
- mpr("A rock staircase leading down.");
- break;
- case DNGN_STONE_STAIRS_DOWN_I:
- case DNGN_STONE_STAIRS_DOWN_II:
- case DNGN_STONE_STAIRS_DOWN_III:
- mpr("A stone staircase leading down.");
- break;
- case DNGN_ROCK_STAIRS_UP:
- mpr("A rock staircase leading upwards.");
- break;
- case DNGN_STONE_STAIRS_UP_I:
- case DNGN_STONE_STAIRS_UP_II:
- case DNGN_STONE_STAIRS_UP_III:
- mpr("A stone staircase leading up.");
- break;
- case DNGN_ENTER_HELL:
- mpr("A gateway to hell.");
- break;
- case DNGN_BRANCH_STAIRS:
- mpr("A staircase to a branch level.");
- break;
- case DNGN_TRAP_MECHANICAL:
- case DNGN_TRAP_MAGICAL:
- case DNGN_TRAP_III:
- for (trf = 0; trf < MAX_TRAPS; trf++)
- {
- if (env.trap[trf].x == mx
- && env.trap[trf].y == my)
- {
- break;
- }
-
- if (trf == MAX_TRAPS - 1)
- {
- mpr("Error - couldn't find that trap.");
- error_message_to_player();
- break;
- }
- }
-
- switch (env.trap[trf].type)
- {
- case TRAP_DART:
- mpr("A dart trap.");
- break;
- case TRAP_ARROW:
- mpr("An arrow trap.");
- break;
- case TRAP_SPEAR:
- mpr("A spear trap.");
- break;
- case TRAP_AXE:
- mpr("An axe trap.");
- break;
- case TRAP_TELEPORT:
- mpr("A teleportation trap.");
- break;
- case TRAP_AMNESIA:
- mpr("An amnesia trap.");
- break;
- case TRAP_BLADE:
- mpr("A blade trap.");
- break;
- case TRAP_BOLT:
- mpr("A bolt trap.");
- break;
- case TRAP_ZOT:
- mpr("A Zot trap.");
- break;
- case TRAP_NEEDLE:
- mpr("A needle trap.");
- break;
- default:
- mpr("An undefined trap. Huh?");
- error_message_to_player();
- break;
- }
- break;
- case DNGN_ENTER_SHOP:
- mpr(shop_name(mx, my));
- break;
- case DNGN_ENTER_LABYRINTH:
- mpr("A labyrinth entrance.");
- break;
- case DNGN_ENTER_DIS:
- mpr("A gateway to the Iron City of Dis.");
- break;
- case DNGN_ENTER_GEHENNA:
- mpr("A gateway to Gehenna.");
- break;
- case DNGN_ENTER_COCYTUS:
- mpr("A gateway to the freezing wastes of Cocytus.");
- break;
- case DNGN_ENTER_TARTARUS:
- mpr("A gateway to the decaying netherworld of Tartarus.");
- break;
- case DNGN_ENTER_ABYSS:
- mpr("A gateway to the infinite Abyss.");
- break;
- case DNGN_EXIT_ABYSS:
- mpr("A gateway leading out of the Abyss.");
- break;
- case DNGN_STONE_ARCH:
- mpr("An empty arch of ancient stone.");
- break;
- case DNGN_ENTER_PANDEMONIUM:
- mpr("A gate leading to the halls of Pandemonium.");
- break;
- case DNGN_EXIT_PANDEMONIUM:
- mpr("A gate leading out of Pandemonium.");
- break;
- case DNGN_TRANSIT_PANDEMONIUM:
- mpr("A gate leading to another region of Pandemonium.");
- break;
- case DNGN_ENTER_ORCISH_MINES:
- mpr("A staircase to the Orcish Mines.");
- break;
- case DNGN_ENTER_HIVE:
- mpr("A staircase to the Hive.");
- break;
- case DNGN_ENTER_LAIR:
- mpr("A staircase to the Lair.");
- break;
- case DNGN_ENTER_SLIME_PITS:
- mpr("A staircase to the Slime Pits.");
- break;
- case DNGN_ENTER_VAULTS:
- mpr("A staircase to the Vaults.");
- break;
- case DNGN_ENTER_CRYPT:
- mpr("A staircase to the Crypt.");
- break;
- case DNGN_ENTER_HALL_OF_BLADES:
- mpr("A staircase to the Hall of Blades.");
- break;
- case DNGN_ENTER_ZOT:
- mpr("A gate to the Realm of Zot.");
- break;
- case DNGN_ENTER_TEMPLE:
- mpr("A staircase to the Ecumenical Temple.");
- break;
- case DNGN_ENTER_SNAKE_PIT:
- mpr("A staircase to the Snake Pit.");
- break;
- case DNGN_ENTER_ELVEN_HALLS:
- mpr("A staircase to the Elven Halls.");
- break;
- case DNGN_ENTER_TOMB:
- mpr("A staircase to the Tomb.");
- break;
- case DNGN_ENTER_SWAMP:
- mpr("A staircase to the Swamp.");
- break;
- case DNGN_RETURN_FROM_ORCISH_MINES:
- case DNGN_RETURN_FROM_HIVE:
- case DNGN_RETURN_FROM_LAIR:
- case DNGN_RETURN_FROM_VAULTS:
- case DNGN_RETURN_FROM_TEMPLE:
- mpr("A staircase back to the Dungeon.");
- break;
- case DNGN_RETURN_FROM_SLIME_PITS:
- case DNGN_RETURN_FROM_SNAKE_PIT:
- case DNGN_RETURN_FROM_SWAMP:
- mpr("A staircase back to the Lair.");
- break;
- case DNGN_RETURN_FROM_CRYPT:
- case DNGN_RETURN_FROM_HALL_OF_BLADES:
- mpr("A staircase back to the Vaults.");
- break;
- case DNGN_RETURN_FROM_ELVEN_HALLS:
- mpr("A staircase back to the Mines.");
- break;
- case DNGN_RETURN_FROM_TOMB:
- mpr("A staircase back to the Crypt.");
- break;
- case DNGN_RETURN_FROM_ZOT:
- mpr("A gate leading back out of this place.");
- break;
- case DNGN_ALTAR_ZIN:
- mpr("A glowing white marble altar of Zin.");
- break;
- case DNGN_ALTAR_SHINING_ONE:
- mpr("A glowing golden altar of the Shining One.");
- break;
- case DNGN_ALTAR_KIKUBAAQUDGHA:
- mpr("An ancient bone altar of Kikubaaqudgha.");
- break;
- case DNGN_ALTAR_YREDELEMNUL:
- mpr("A basalt altar of Yredelemnul.");
- break;
- case DNGN_ALTAR_XOM:
- mpr("A shimmering altar of Xom.");
- break;
- case DNGN_ALTAR_VEHUMET:
- mpr("A shining altar of Vehumet.");
- break;
- case DNGN_ALTAR_OKAWARU:
- mpr("An iron altar of Okawaru.");
- break;
- case DNGN_ALTAR_MAKHLEB:
- mpr("A burning altar of Makhleb.");
- break;
- case DNGN_ALTAR_SIF_MUNA:
- mpr("A deep blue altar of Sif Muna.");
- break;
- case DNGN_ALTAR_TROG:
- mpr("A bloodstained altar of Trog.");
- break;
- case DNGN_ALTAR_NEMELEX_XOBEH:
- mpr("A sparkling altar of Nemelex Xobeh.");
- break;
- case DNGN_ALTAR_ELYVILON:
- mpr("A silver altar of Elyvilon.");
- break;
- case DNGN_BLUE_FOUNTAIN:
- mpr("A fountain of clear blue water.");
- break;
- case DNGN_SPARKLING_FOUNTAIN:
- mpr("A fountain of sparkling water.");
- break;
- case DNGN_DRY_FOUNTAIN_I:
- case DNGN_DRY_FOUNTAIN_II:
- case DNGN_DRY_FOUNTAIN_IV:
- case DNGN_DRY_FOUNTAIN_VI:
- case DNGN_DRY_FOUNTAIN_VIII:
- case DNGN_PERMADRY_FOUNTAIN:
- mpr("A dry fountain.");
- break;
- }
+ std::string feature_desc = feature_description(mx, my);
+ mpr(feature_desc.c_str());
}
diff --git a/trunk/source/direct.h b/trunk/source/direct.h
index 2bd5a06cdd..e591107578 100644
--- a/trunk/source/direct.h
+++ b/trunk/source/direct.h
@@ -37,5 +37,11 @@ void direction( struct dist &moves, int restricts = DIR_NONE,
void look_around( struct dist &moves, bool justLooking, int first_move = -1,
int mode = TARG_ANY );
+bool in_los_bounds(int x, int y);
+bool in_viewport_bounds(int x, int y);
+bool in_los(int x, int y);
+bool in_vlos(int x, int y);
+
+std::string feature_description(int mx, int my);
#endif
diff --git a/trunk/source/dungeon.cc b/trunk/source/dungeon.cc
index ce002f21d2..a18ab7c726 100644
--- a/trunk/source/dungeon.cc
+++ b/trunk/source/dungeon.cc
@@ -162,7 +162,7 @@ void builder(int level_number, char level_type)
int i; // generic loop variable
int x,y; // generic map loop variables
- srandom(time(NULL));
+ // srandom(time(NULL));
// blank level with DNGN_ROCK_WALL
make_box(0,0,GXM-1,GYM-1,DNGN_ROCK_WALL,DNGN_ROCK_WALL);
@@ -450,6 +450,9 @@ int items( int allow_uniques, // not just true-false,
mitm[p].x = 0;
mitm[p].y = 0;
mitm[p].link = NON_ITEM;
+ mitm[p].slot = 0;
+ mitm[p].orig_monnum = 0;
+ mitm[p].orig_place = 0;
// cap item_level unless an acquirement-level item {dlb}:
if (item_level > 50 && item_level != MAKE_GOOD_ITEM)
@@ -2509,6 +2512,8 @@ void give_item(int mid, int level_number) //mv: cleanup+minor changes
mitm[bp].plus = 0;
mitm[bp].plus2 = 0;
mitm[bp].special = 0;
+ mitm[bp].orig_place = 0;
+ mitm[bp].orig_monnum = 0;
// this flags things to "goto give_armour" below ... {dlb}
mitm[bp].base_type = 101;
@@ -3139,6 +3144,8 @@ void give_item(int mid, int level_number) //mv: cleanup+minor changes
mitm[bp].x = 0;
mitm[bp].y = 0;
mitm[bp].link = NON_ITEM;
+ mitm[bp].orig_place = 0;
+ mitm[bp].orig_monnum = 0;
item_race = MAKE_ITEM_RANDOM_RACE;
give_level = 1 + (level_number / 2);
@@ -7540,6 +7547,14 @@ static int box_room_doors( int bx1, int bx2, int by1, int by2, int new_doors)
return doors_placed;
}
+ // Avoid an infinite loop if there are not enough good spots. --KON
+ j = 0;
+ for (i=0; i<spot_count; i++)
+ if (good_doors[i] == 1)
+ j++;
+ if (new_doors > j)
+ new_doors = j;
+
while(new_doors > 0 && spot_count > 0)
{
spot = random2(spot_count);
diff --git a/trunk/source/effects.cc b/trunk/source/effects.cc
index c2645e1b73..16517cba40 100644
--- a/trunk/source/effects.cc
+++ b/trunk/source/effects.cc
@@ -95,7 +95,7 @@ void banished(unsigned char gate_type)
// this is to ensure that you're standing on a suitable space (67)
grd[you.x_pos][you.y_pos] = gate_type;
- down_stairs(true, you.your_level); // heh heh
+ down_stairs(true, you.your_level, true); // heh heh
untag_followers(); // safety
} // end banished()
@@ -434,7 +434,7 @@ void random_uselessness(unsigned char ru, unsigned char sc_read_2)
return;
} // end random_uselessness()
-bool acquirement(unsigned char force_class)
+bool acquirement(unsigned char force_class, int agent)
{
int thing_created = 0;
int iteration = 0;
@@ -620,6 +620,18 @@ bool acquirement(unsigned char force_class)
case SP_OGRE:
case SP_OGRE_MAGE:
case SP_TROLL:
+ case SP_RED_DRACONIAN:
+ case SP_WHITE_DRACONIAN:
+ case SP_GREEN_DRACONIAN:
+ case SP_GOLDEN_DRACONIAN:
+ case SP_GREY_DRACONIAN:
+ case SP_BLACK_DRACONIAN:
+ case SP_PURPLE_DRACONIAN:
+ case SP_MOTTLED_DRACONIAN:
+ case SP_PALE_DRACONIAN:
+ case SP_UNK0_DRACONIAN:
+ case SP_UNK1_DRACONIAN:
+ case SP_UNK2_DRACONIAN:
case SP_SPRIGGAN:
if (type_wanted == ARM_GLOVES || type_wanted == ARM_BOOTS)
{
@@ -1235,7 +1247,10 @@ bool acquirement(unsigned char force_class)
// move_item_to_grid works (doesn't create a new item ever),
// but we're checking it anyways. -- bwr
if (thing_created != NON_ITEM)
+ {
canned_msg(MSG_SOMETHING_APPEARS);
+ origin_acquired(mitm[thing_created], agent);
+ }
}
// Well, the item may have fallen in the drink, but the intent is
diff --git a/trunk/source/effects.h b/trunk/source/effects.h
index 039ba80c0c..b7442a493e 100644
--- a/trunk/source/effects.h
+++ b/trunk/source/effects.h
@@ -50,7 +50,7 @@ void random_uselessness(unsigned char ru, unsigned char sc_read_2);
/* ***********************************************************************
* called from: acr - decks - item_use - religion
* *********************************************************************** */
-bool acquirement(unsigned char force_class);
+bool acquirement(unsigned char force_class, int agent);
// last updated 12may2000 {dlb}
diff --git a/trunk/source/enum.h b/trunk/source/enum.h
index a1b76e0422..1306540f2b 100644
--- a/trunk/source/enum.h
+++ b/trunk/source/enum.h
@@ -126,6 +126,42 @@ enum ABILITY_FLAGS
ABFLAG_PERMANENT_MP = 0x00000040 // costs permanent MPs
};
+enum ACTIVITY
+{
+ ACT_NONE = 0,
+ ACT_MULTIDROP,
+ ACT_RUNNING,
+ ACT_TRAVELING,
+ ACT_MACRO,
+
+ ACT_ACTIVITY_COUNT
+};
+
+enum ACT_INTERRUPT
+{
+ AI_FORCE_INTERRUPT = 0, // Forcibly kills any activity
+ AI_KEYPRESS = 0x01,
+ AI_FULL_HP = 0x02, // Player is fully healed
+ AI_FULL_MP = 0x04, // Player has recovered all mp
+ AI_STATUE = 0x08, // Bad statue has come into view
+ AI_HUNGRY = 0x10, // Hunger increased
+ AI_MESSAGE = 0x20, // Message was displayed
+ AI_HP_LOSS = 0x40,
+ AI_BURDEN_CHANGE = 0x80,
+ AI_STAT_CHANGE = 0x100,
+ AI_SEE_MONSTER = 0x200,
+ AI_TELEPORT = 0x400,
+};
+
+enum AI_PAYLOAD
+{
+ AIP_NONE,
+ AIP_INT,
+ AIP_STRING,
+ AIP_MONSTER,
+ AIP_HP_LOSS,
+};
+
enum AMMUNITION_DESCRIPTIONS
{
DAMMO_ORCISH = 3, // 3
@@ -133,6 +169,21 @@ enum AMMUNITION_DESCRIPTIONS
DAMMO_DWARVEN // 5
};
+// Various ways to get the acquirement effect.
+enum AQ_AGENTS
+{
+ AQ_SCROLL = 0,
+
+ // Empty space for the gods
+
+ AQ_CARD_ACQUISITION = 100,
+ AQ_CARD_VIOLENCE,
+ AQ_CARD_PROTECTION,
+ AQ_CARD_KNOWLEDGE,
+
+ AQ_WIZMODE = 200,
+};
+
enum ARMOUR
{
ARM_ROBE, // 0
@@ -545,7 +596,16 @@ enum COMMANDS
CMD_QUIT,
CMD_WIZARD,
CMD_DESTROY_ITEM,
- CMD_OBSOLETE_INVOKE
+ CMD_OBSOLETE_INVOKE,
+
+ CMD_MARK_STASH,
+ CMD_FORGET_STASH,
+ CMD_SEARCH_STASHES,
+ CMD_EXPLORE,
+ CMD_INTERLEVEL_TRAVEL,
+ CMD_FIX_WAYPOINT,
+
+ CMD_CLEAR_MAP,
};
enum CONFIRM_LEVEL
@@ -645,6 +705,12 @@ enum DIRECTION // (unsigned char) you.char_direction
DIR_ASCENDING = 1 // 1 - change and lose savefile compatibility (!!!)
};
+enum DROP_MODE
+{
+ DM_SINGLE,
+ DM_MULTI
+};
+
enum DUNGEON_FEATURES // (unsigned char) grd[][]
{
DNGN_UNSEEN, // 0
@@ -904,6 +970,7 @@ enum FLUSH_REASONS
FLUSH_ON_FAILURE, // spell/ability failed to cast
FLUSH_BEFORE_COMMAND, // flush before getting a command
FLUSH_ON_MESSAGE, // flush when printing a message
+ FLUSH_LUA, // flush when Lua wants us to
NUM_FLUSH_REASONS
};
@@ -1116,6 +1183,8 @@ enum ITEM_STATUS_FLAGS // per item flags: ie. ident status, cursed status
ISFLAG_RANDART = 0x00001000, // special value is seed
ISFLAG_UNRANDART = 0x00002000, // is an unrandart
ISFLAG_ARTEFACT_MASK = 0x00003000, // randart or unrandart
+ ISFLAG_DROPPED = 0x00004000, // dropped item (no autopickup)
+ ISFLAG_THROWN = 0x00008000, // thrown missile weapon
ISFLAG_NO_DESC = 0x00000000, // used for clearing these flags
ISFLAG_GLOWING = 0x00010000, // weapons or armour
@@ -1151,6 +1220,20 @@ enum ITEM_MAKE_SPECIES // used only for race during creation
MAKE_ITEM_RANDOM_RACE = 250
};
+enum ITEM_ORIGIN_DUMP_SELECT
+{
+ IODS_PRICE = 0, // Extra info is provided based on price
+ IODS_ARTIFACTS = 1, // Extra information on artifacts
+ IODS_EGO_ARMOUR = 2,
+ IODS_EGO_WEAPON = 4,
+ IODS_JEWELLERY = 8,
+ IODS_RUNES = 16,
+ IODS_RODS = 32,
+ IODS_STAVES = 64,
+ IODS_BOOKS = 128,
+ IODS_EVERYTHING = 0xFF
+};
+
enum ITEM_TYPE_ID // used for first index of id[4][50]
{
IDTYPE_WANDS = 0,
@@ -1276,6 +1359,14 @@ enum KILLBY
NUM_KILLBY
};
+enum KillCategory
+{
+ KC_YOU,
+ KC_FRIENDLY,
+ KC_OTHER,
+ KC_NCATEGORIES
+};
+
enum KILLER // monster_die(), thing_thrown
{
KILL_YOU = 1, // 1
@@ -2153,6 +2244,12 @@ enum OBJECT_CLASSES // (unsigned char) mitm[].base_type
// for blanket random sub_type .. see dungeon::items()
};
+enum OBJECT_SELECTORS
+{
+ OSEL_ANY = -1,
+ OSEL_WIELD = -2,
+};
+
enum ORBS
{
ORB_ZOT // 0
@@ -2762,6 +2859,12 @@ enum SPELL_TYPES //jmf: 24jul2000: changed from integer-list to bitfield
SPTYP_RANDOM = 1<<14
};
+enum SLOT_SELECT_MODES
+{
+ SS_FORWARD = 0,
+ SS_BACKWARD = 1,
+};
+
enum STATS
{
STAT_STRENGTH, // 0
diff --git a/trunk/source/externs.h b/trunk/source/externs.h
index 369f66c340..91b66e5f47 100644
--- a/trunk/source/externs.h
+++ b/trunk/source/externs.h
@@ -17,12 +17,19 @@
#define EXTERNS_H
#include <queue>
+#include <vector>
+#include <list>
+#include <string>
+
+#include <map>
#include <time.h>
#include "defines.h"
#include "enum.h"
#include "FixAry.h"
+#include "Kills.h"
+#include "libutil.h"
#include "message.h"
#define INFO_SIZE 200 // size of message buffers
@@ -64,12 +71,65 @@ const int kPathLen = 256;
// penalty (Xom's granted or from a deck of cards).
#define NO_BERSERK_PENALTY -1
+struct monsters;
+struct ait_hp_loss;
+
+struct activity_interrupt_t
+{
+ AI_PAYLOAD apt;
+ const void *data;
+
+ activity_interrupt_t()
+ : apt(AIP_NONE), data(NULL)
+ {
+ }
+ activity_interrupt_t(const int *i)
+ : apt(AIP_INT), data(i)
+ {
+ }
+ activity_interrupt_t(const char *s)
+ : apt(AIP_STRING), data(s)
+ {
+ }
+ activity_interrupt_t(const std::string &s)
+ : apt(AIP_STRING), data(s.c_str())
+ {
+ }
+ activity_interrupt_t(const monsters *m)
+ : apt(AIP_MONSTER), data(m)
+ {
+ }
+ activity_interrupt_t(const ait_hp_loss *ahl)
+ : apt(AIP_HP_LOSS), data(ahl)
+ {
+ }
+ activity_interrupt_t(const activity_interrupt_t &a)
+ : apt(a.apt), data(a.data)
+ {
+ }
+};
+
+struct ait_hp_loss
+{
+ int hp;
+ int hurt_type; // KILLED_BY_POISON, etc.
+
+ ait_hp_loss(int _hp, int _ht) : hp(_hp), hurt_type(_ht) { }
+};
+
struct coord_def
{
int x;
int y;
// coord_def( int x_in = 0, int y_in = 0 ) : x(x_in), y(y_in) {};
+ bool operator == (const coord_def &other) const {
+ return x == other.x && y == other.y;
+ }
+
+ bool operator != (const coord_def &other) const {
+ return x != other.x || y != other.y;
+ }
};
struct dice_def
@@ -163,11 +223,42 @@ struct item_def
short x; // x-location; for inventory items = -1
short y; // y-location; for inventory items = -1
short link; // link to next item; for inventory items = slot
+ short slot; // Inventory letter
+
+ unsigned short orig_place;
+ short orig_monnum;
+
+ item_def() : base_type(0), sub_type(0), plus(0), plus2(0),
+ special(0L), colour(0), flags(0L), quantity(0),
+ x(0), y(0), link(0), slot(0), orig_place(0),
+ orig_monnum(0)
+ {
+ }
};
+class input_history
+{
+public:
+ input_history(size_t size);
+
+ void new_input(const std::string &s);
+ void clear();
+
+ const std::string *prev();
+ const std::string *next();
+
+ void go_end();
+private:
+ typedef std::list<std::string> string_list;
+
+ string_list history;
+ string_list::iterator pos;
+ size_t maxsize;
+};
struct player
{
+ ACTIVITY activity; // The current multiturn activity, usually set to ACT_NONE
char turn_is_over; // flag signaling that player has performed a timed action
unsigned char prev_targ;
@@ -177,8 +268,13 @@ struct player
char run_x;
char run_y;
+
+ // Coordinates of last travel target; note that this is never used by
+ // travel itself, only by the level-map to remember the last travel target.
+ short travel_x, travel_y;
+
FixedVector< run_check_dir, 3 > run_check; // array of grids to check
- char running;
+ signed char running; // Nonzero if running/traveling.
char special_wield;
char deaths_door;
@@ -293,6 +389,9 @@ struct player
FixedArray<unsigned char, 5, 50> item_description;
FixedVector<unsigned char, 50> unique_items;
FixedVector<unsigned char, 50> unique_creatures;
+
+ KillMaster kills;
+
char level_type;
char where_are_you;
@@ -409,7 +508,7 @@ struct crawl_environment
FixedArray< int, GXM, GYM > igrid; // item grid
FixedArray< unsigned char, GXM, GYM > cgrid; // cloud grid
- FixedArray< unsigned char, GXM, GYM > map; // discovered terrain
+ FixedArray< unsigned short, GXM, GYM > map; // discovered terrain
FixedArray< unsigned int, 19, 19> show; // view window char
FixedArray< unsigned short, 19, 19> show_col; // view window colour
@@ -453,10 +552,98 @@ struct system_environment
extern system_environment SysEnv;
+struct message_filter
+{
+ int channel; // Use -1 to match any channel.
+ text_pattern pattern; // Empty pattern matches any message
+
+ message_filter( int ch, const std::string &s )
+ : channel(ch), pattern(s)
+ {
+ }
+
+ message_filter( const std::string &s ) : channel(-1), pattern(s) { }
+
+ bool is_filtered( int ch, const std::string &s ) const {
+ bool channel_match = ch == channel || channel == -1;
+ if (!channel_match || pattern.empty())
+ return channel_match;
+ return pattern.matches(s);
+ }
+
+};
+
+struct sound_mapping
+{
+ text_pattern pattern;
+ std::string soundfile;
+};
+
+struct colour_mapping
+{
+ text_pattern pattern;
+ int colour;
+};
+
+class formatted_string
+{
+public:
+ formatted_string() : ops() { }
+ formatted_string(const std::string &s);
+
+ operator std::string() const;
+ void display(int start = 0, int end = -1) const;
+ std::string tostring(int start = 0, int end = -1) const;
+
+ void cprintf(const char *s, ...);
+ void cprintf(const std::string &s);
+ void gotoxy(int x, int y);
+ void textcolor(int color);
+
+private:
+ enum fs_op_type
+ {
+ FSOP_COLOUR,
+ FSOP_CURSOR,
+ FSOP_TEXT,
+ };
+
+ struct fs_op
+ {
+ fs_op_type type;
+ int x, y;
+ std::string text;
+
+ fs_op(int color)
+ : type(FSOP_COLOUR), x(color), y(-1), text()
+ {
+ }
+
+ fs_op(int cx, int cy)
+ : type(FSOP_CURSOR), x(cx), y(cy), text()
+ {
+ }
+
+ fs_op(const std::string &s)
+ : type(FSOP_TEXT), x(-1), y(-1), text(s)
+ {
+ }
+
+ operator fs_op_type () const
+ {
+ return type;
+ }
+ void display() const;
+ };
+
+ std::vector<fs_op> ops;
+};
+
struct game_options
{
long autopickups; // items to autopickup
bool verbose_dump; // make character dumps contain more detail
+ bool detailed_stat_dump; // add detailed stat and resist dump
bool colour_map; // add colour to the map
bool clean_map; // remove unseen clouds/monsters
bool show_uncursed; // label known uncursed items as "uncursed"
@@ -480,8 +667,10 @@ struct game_options
char cls; // preselected class
bool terse_hand; // use terse description for wielded item
bool delay_message_clear; // avoid clearing messages each turn
- unsigned int friend_brand; // Attribute for branding friendly monsters
+ unsigned friend_brand; // Attribute for branding friendly monsters
bool no_dark_brand; // Attribute for branding friendly monsters
+ bool macro_meta_entry; // Allow user to use \{foo} sequences when
+ // creating macros
int fire_items_start; // index of first item for fire command
FixedVector<int, NUM_FIRE_TYPES> fire_order; // order for 'f' command
@@ -500,6 +689,89 @@ struct game_options
// internal use only:
int sc_entries; // # of score entries
int sc_format; // Format for score entries
+
+ std::vector<text_pattern> banned_objects; // Objects we'll never pick up
+ bool pickup_thrown; // Pickup thrown missiles
+ bool pickup_dropped; // Pickup dropped objects
+ int travel_delay; // How long to pause between travel moves
+
+ std::vector<message_filter> stop_travel; // Messages that stop travel
+
+ int stash_tracking; // How stashes are tracked
+
+ bool travel_colour; // Colour levelmap using travel information?
+ int travel_stair_cost;
+
+ int travel_exclude_radius2; // Square of the travel exclude radius
+ bool show_waypoints;
+
+ bool item_colour; // Colour items on level map
+
+ unsigned detected_monster_colour; // Colour of detected monsters
+ unsigned detected_item_colour; // Colour of detected items
+ unsigned remembered_monster_colour; // Colour for monsters remembered
+ // on the map.
+
+ unsigned heap_brand; // Highlight heaps of items in the playing area
+ unsigned stab_brand; // Highlight monsters that are stabbable
+ unsigned may_stab_brand; // Highlight potential stab candidates
+
+ int explore_stop; // Stop exploring if a previously unseen
+ // item comes into view
+
+ std::vector<sound_mapping> sound_mappings;
+ std::vector<colour_mapping> menu_colour_mappings;
+
+ int dump_kill_places; // How to dump place information for kills.
+ int dump_message_count; // How many old messages to dump
+
+ int dump_item_origins; // Show where items came from?
+ int dump_item_origin_price;
+
+ bool target_zero_exp; // If true, targeting targets zero-exp
+ // monsters.
+ bool target_wrap; // Wrap around from last to first target
+ bool target_oos; // 'x' look around can target out-of-LOS
+ bool target_los_first; // 'x' look around first goes to visible
+ // objects/features, then goes to stuff
+ // outside LOS.
+
+ int drop_mode; // Controls whether single or multidrop
+ // is the default.
+
+ bool easy_exit_menu; // Menus are easier to get out of
+
+ int assign_item_slot; // How free slots are assigned
+
+ std::vector<text_pattern> drop_filter;
+
+ FixedVector< unsigned, ACT_ACTIVITY_COUNT > activity_interrupts;
+
+ // Previous startup options
+ bool remember_name; // Remember and reprompt with last name
+
+ bool dos_use_background_intensity;
+
+ int level_map_cursor_step; // The cursor increment in the level
+ // map.
+
+ // If the player prefers to merge kill records, this option can do that.
+ int kill_map[KC_NCATEGORIES];
+
+ typedef std::map<std::string, std::string> opt_map;
+ opt_map named_options; // All options not caught above are
+ // recorded here.
+
+ ///////////////////////////////////////////////////////////////////////
+ // These options cannot be directly set by the user. Instead they're
+ // set indirectly to the choices the user made for the last character
+ // created. XXX: Isn't there a better place for these?
+ std::string prev_name;
+ char prev_race;
+ char prev_cls;
+ int prev_ck, prev_dk, prev_pr;
+ int prev_weapon;
+ bool prev_randpick;
};
extern game_options Options;
diff --git a/trunk/source/fight.cc b/trunk/source/fight.cc
index ab20364ffe..6a93de441e 100644
--- a/trunk/source/fight.cc
+++ b/trunk/source/fight.cc
@@ -1676,7 +1676,9 @@ void you_attack(int monster_attacked, bool unarmed_attacks)
/* no punching with a shield or 2-handed wpn, except staves */
if (bearing_shield || coinflip()
- || (ur_armed && hands_reqd == HANDS_TWO_HANDED))
+ || (ur_armed && hands_reqd == HANDS_TWO_HANDED
+ && you.inv[weapon].base_type != OBJ_STAVES
+ && you.inv[weapon].sub_type != WPN_QUARTERSTAFF) )
{
continue;
}
@@ -2866,7 +2868,7 @@ commented out for now
break;
}
- if (coinflip())
+ if (coinflip() && you.level_type != LEVEL_ABYSS)
{
banished(DNGN_ENTER_ABYSS);
break;
@@ -3705,6 +3707,8 @@ static int weapon_type_modify( int weapnum, char noise[80], char noise2[80],
strcpy( noise, "slash" );
else if (damage < HIT_STRONG)
strcpy( noise, "slice" );
+ else
+ strcpy( noise, "shred" );
break;
case TRAN_ICE_BEAST:
case TRAN_STATUE:
diff --git a/trunk/source/files.cc b/trunk/source/files.cc
index 4df8035f16..0fdd4ff381 100644
--- a/trunk/source/files.cc
+++ b/trunk/source/files.cc
@@ -26,6 +26,7 @@
#include "files.h"
#include <string.h>
+#include <string>
#include <stdlib.h>
#include <stdio.h>
#include <ctype.h>
@@ -54,9 +55,14 @@
#include <sys/stat.h>
#endif
+#ifdef __MINGW32__
+#include <io.h>
+#endif
+
#include "externs.h"
#include "cloud.h"
+#include "clua.h"
#include "debug.h"
#include "dungeon.h"
#include "itemname.h"
@@ -69,8 +75,10 @@
#include "player.h"
#include "randart.h"
#include "skills2.h"
+#include "stash.h"
#include "stuff.h"
#include "tags.h"
+#include "travel.h"
#include "wpn-misc.h"
void save_level(int level_saved, bool was_a_labyrinth, char where_were_you);
@@ -197,6 +205,28 @@ static void restore_tagged_file( FILE *restoreFile, int fileType,
static void load_ghost();
+std::string get_savedir_filename(const char *prefix, const char *suffix,
+ const char *extension)
+{
+ char filename[1200];
+#ifdef SAVE_DIR_PATH
+ snprintf(filename, sizeof filename, SAVE_DIR_PATH "%s%d%s.%s",
+ prefix, (int) getuid(), suffix, extension);
+#else
+ snprintf(filename, sizeof filename, "%s%s.%s",
+ prefix, suffix, extension);
+#ifdef DOS
+ strupr(filename);
+#endif
+#endif
+ return std::string(filename);
+}
+
+std::string get_prefs_filename()
+{
+ return get_savedir_filename("start", "ns", "prf");
+}
+
void make_filename( char *buf, const char *prefix, int level, int where,
bool isLabyrinth, bool isGhost )
{
@@ -268,6 +298,41 @@ static void write_tagged_file( FILE *dataFile, char majorVersion,
}
}
+bool travel_load_map( char branch, int absdepth )
+{
+ char cha_fil[kFileNameSize];
+
+ make_filename( cha_fil, you.your_name, absdepth, branch,
+ false, false );
+#ifdef DOS
+ strupr(cha_fil);
+#endif
+
+ // Try to open level savefile.
+ FILE *levelFile = fopen(cha_fil, "rb");
+ if (!levelFile)
+ return false;
+
+ // BEGIN -- must load the old level : pre-load tasks
+
+ // LOAD various tags
+ char majorVersion;
+ char minorVersion;
+
+ if (!determine_level_version( levelFile, majorVersion, minorVersion )
+ || majorVersion != 4)
+ {
+ fclose(levelFile);
+ return false;
+ }
+
+ tag_read(levelFile, minorVersion);
+
+ fclose( levelFile );
+
+ return true;
+}
+
void load( unsigned char stair_taken, int load_mode, bool was_a_labyrinth,
char old_level, char where_were_you2 )
{
@@ -679,10 +744,10 @@ found_stair:
// This should fix the "monster occuring under the player" bug?
if (mgrd[you.x_pos][you.y_pos] != NON_MONSTER)
monster_teleport(&menv[mgrd[you.x_pos][you.y_pos]], true);
-
+ /*
if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
grd[you.x_pos][you.y_pos] = DNGN_FLOOR;
-
+ */
following = 0;
fmenv = -1;
@@ -899,8 +964,11 @@ void save_level(int level_saved, bool was_a_labyrinth, char where_were_you)
// 4.3 changes to make the item structure more sane
// 4.4 changes to the ghost save section
// 4.5 spell and ability letter arrays
+ // 4.6 inventory slots of items
+ // 4.7 origin tracking for items
+ // 4.8 widened env.map to 2 bytes
- write_tagged_file( saveFile, 4, 5, TAGTYPE_LEVEL );
+ write_tagged_file( saveFile, 4, 8, TAGTYPE_LEVEL );
fclose(saveFile);
@@ -912,6 +980,14 @@ void save_level(int level_saved, bool was_a_labyrinth, char where_were_you)
void save_game(bool leave_game)
{
char charFile[kFileNameSize];
+#ifdef STASH_TRACKING
+ char stashFile[kFileNameSize + 4];
+#endif
+ char killFile[kFileNameSize + 4];
+ char travelCacheFile[kFileNameSize + 4];
+#ifdef CLUA_BINDINGS
+ char luaFile[kFileNameSize + 4];
+#endif
#ifdef SAVE_PACKAGE_CMD
char cmd_buff[1024];
@@ -923,18 +999,97 @@ void save_game(bool leave_game)
snprintf( cmd_buff, sizeof(cmd_buff),
SAVE_PACKAGE_CMD, name_buff, name_buff );
+#ifdef STASH_TRACKING
+ strcpy(stashFile, name_buff);
+#endif
+#ifdef CLUA_BINDINGS
+ strcpy(luaFile, name_buff);
+#endif
+ strcpy(killFile, name_buff);
+ strcpy(travelCacheFile, name_buff);
snprintf( charFile, sizeof(charFile),
"%s.sav", name_buff );
#else
strncpy(charFile, you.your_name, kFileNameLen);
charFile[kFileNameLen] = 0;
+
+#ifdef STASH_TRACKING
+ strcpy(stashFile, charFile);
+#endif
+#ifdef CLUA_BINDINGS
+ strcpy(luaFile, charFile);
+#endif
+ strcpy(killFile, charFile);
+ strcpy(travelCacheFile, charFile);
strcat(charFile, ".sav");
#ifdef DOS
strupr(charFile);
+#ifdef STASH_TRACKING
+ strupr(stashFile);
+#endif
+#ifdef CLUA_BINDINGS
+ strupr(luaFile);
#endif
+ strupr(killFile);
+ strupr(travelCacheFile);
#endif
+#endif
+
+#ifdef STASH_TRACKING
+ strcat(stashFile, ".st");
+#endif
+#ifdef CLUA_BINDINGS
+ strcat(luaFile, ".lua");
+#endif
+ strcat(killFile, ".kil");
+ strcat(travelCacheFile, ".tc");
+
+#ifdef STASH_TRACKING
+ FILE *stashf = fopen(stashFile, "wb");
+ if (stashf)
+ {
+ stashes.save(stashf);
+ fclose(stashf);
+
+#ifdef SHARED_FILES_CHMOD_PRIVATE
+ // change mode (unices)
+ chmod(stashFile, SHARED_FILES_CHMOD_PRIVATE);
+#endif
+ }
+#endif // STASH_TRACKING
+
+#ifdef CLUA_BINDINGS
+ clua.save(luaFile);
+#ifdef SHARED_FILES_CHMOD_PRIVATE
+ // change mode; note that luaFile may not exist
+ chmod(luaFile, SHARED_FILES_CHMOD_PRIVATE);
+#endif
+#endif // CLUA_BINDINGS
+
+ FILE *travelf = fopen(travelCacheFile, "wb");
+ if (travelf)
+ {
+ travel_cache.save(travelf);
+ fclose(travelf);
+#ifdef SHARED_FILES_CHMOD_PRIVATE
+ // change mode (unices)
+ chmod(travelCacheFile, SHARED_FILES_CHMOD_PRIVATE);
+#endif
+ }
+
+ FILE *killf = fopen(killFile, "wb");
+ if (killf)
+ {
+ you.kills.save(killf);
+ fclose(killf);
+
+#ifdef SHARED_FILES_CHMOD_PRIVATE
+ // change mode (unices)
+ chmod(killFile, SHARED_FILES_CHMOD_PRIVATE);
+#endif
+ }
FILE *saveFile = fopen(charFile, "wb");
@@ -950,8 +1105,10 @@ void save_game(bool leave_game)
// 4.0 initial genesis of saved format
// 4.1 changes to make the item structure more sane
// 4.2 spell and ability tables
+ // 4.3 added you.magic_contamination (05/03/05)
+ // 4.4 added item origins
- write_tagged_file( saveFile, 4, 2, TAGTYPE_PLAYER );
+ write_tagged_file( saveFile, 4, 4, TAGTYPE_PLAYER );
fclose(saveFile);
@@ -1098,7 +1255,16 @@ void load_ghost(void)
void restore_game(void)
{
char char_f[kFileNameSize];
+ char kill_f[kFileNameSize];
+ char travel_f[kFileNameSize];
+#ifdef STASH_TRACKING
+ char stash_f[kFileNameSize];
+#endif
+#ifdef CLUA_BINDINGS
+ char lua_f[kFileNameSize];
+#endif
+
#ifdef SAVE_DIR_PATH
snprintf( char_f, sizeof(char_f),
SAVE_DIR_PATH "%s%d", you.your_name, (int) getuid() );
@@ -1107,10 +1273,30 @@ void restore_game(void)
char_f[kFileNameLen] = 0;
#endif
+ strcpy(kill_f, char_f);
+ strcpy(travel_f, char_f);
+#ifdef CLUA_BINDINGS
+ strcpy(lua_f, char_f);
+ strcat(lua_f, ".lua");
+#endif
+#ifdef STASH_TRACKING
+ strcpy(stash_f, char_f);
+ strcat(stash_f, ".st");
+#endif
+ strcat(kill_f, ".kil");
+ strcat(travel_f, ".tc");
strcat(char_f, ".sav");
#ifdef DOS
strupr(char_f);
+#ifdef STASH_TRACKING
+ strupr(stash_f);
+#endif
+ strupr(kill_f);
+ strupr(travel_f);
+#ifdef CLUA_BINDINGS
+ strupr(lua_f);
+#endif
#endif
FILE *restoreFile = fopen(char_f, "rb");
@@ -1144,6 +1330,33 @@ void restore_game(void)
}
fclose(restoreFile);
+
+#ifdef STASH_TRACKING
+ FILE *stashFile = fopen(stash_f, "rb");
+ if (stashFile)
+ {
+ stashes.load(stashFile);
+ fclose(stashFile);
+ }
+#endif
+
+#ifdef CLUA_BINDINGS
+ clua.execfile( lua_f );
+#endif // CLUA_BINDINGS
+
+ FILE *travelFile = fopen(travel_f, "rb");
+ if (travelFile)
+ {
+ travel_cache.load(travelFile);
+ fclose(travelFile);
+ }
+
+ FILE *killFile = fopen(kill_f, "rb");
+ if (killFile)
+ {
+ you.kills.load(killFile);
+ fclose(killFile);
+ }
}
static bool determine_version( FILE *restoreFile,
@@ -1863,3 +2076,73 @@ void generate_random_demon(void)
ghost.values[GVAL_SPELL_5] = MS_DIG;
}
} // end generate_random_demon()
+
+// Largest string we'll save
+#define STR_CAP 1000
+
+using std::string;
+
+void writeShort(FILE *file, short s)
+{
+ char data[2];
+ // High byte first - network order
+ data[0] = (char)((s >> 8) & 0xFF);
+ data[1] = (char)(s & 0xFF);
+
+ write2(file, data, sizeof(data));
+}
+
+short readShort(FILE *file)
+{
+ unsigned char data[2];
+ read2(file, (char *) data, 2);
+
+ // High byte first
+ return (((short) data[0]) << 8) | (short) data[1];
+}
+
+void writeByte(FILE *file, unsigned char byte)
+{
+ write2(file, (char *) &byte, sizeof byte);
+}
+
+unsigned char readByte(FILE *file)
+{
+ unsigned char byte;
+ read2(file, (char *) &byte, sizeof byte);
+ return byte;
+}
+
+void writeString(FILE* file, const string &s)
+{
+ int length = s.length();
+ if (length > STR_CAP) length = STR_CAP;
+ writeShort(file, length);
+ write2(file, s.c_str(), length);
+}
+
+string readString(FILE *file)
+{
+ char buf[STR_CAP + 1];
+ short length = readShort(file);
+ if (length)
+ read2(file, buf, length);
+ buf[length] = '\0';
+ return string(buf);
+}
+
+void writeLong(FILE* file, long num)
+{
+ // High word first, network order
+ writeShort(file, (short) ((num >> 16) & 0xFFFFL));
+ writeShort(file, (short) (num & 0xFFFFL));
+}
+
+long readLong(FILE *file)
+{
+ // We need the unsigned short cast even for the high word because we
+ // might be on a system where long is more than 4 bytes, and we don't want
+ // to sign extend the high short.
+ return ((long) (unsigned short) readShort(file)) << 16 |
+ (long) (unsigned short) readShort(file);
+}
diff --git a/trunk/source/files.h b/trunk/source/files.h
index d0b2c68362..a2d357cac8 100644
--- a/trunk/source/files.h
+++ b/trunk/source/files.h
@@ -13,7 +13,8 @@
#define FILES_H
#include "FixAry.h"
-
+#include <stdio.h>
+#include <string>
// referenced in files - newgame - ouch - overmap:
#define MAX_LEVELS 50
@@ -34,6 +35,13 @@ void load( unsigned char stair_taken, bool moving_level,
bool is_new_game, char where_were_you2 );
#endif
+bool travel_load_map( char branch, int absdepth );
+
+std::string get_savedir_filename(const char *pre, const char *suf,
+ const char *ext);
+
+std::string get_prefs_filename();
+
void load( unsigned char stair_taken, int load_mode, bool was_a_labyrinth,
char old_level, char where_were_you2 );
@@ -64,4 +72,21 @@ void save_ghost( bool force = false );
void make_filename( char *buf, const char *prefix, int level, int where,
bool isLabyrinth, bool isGhost );
+
+void writeShort(FILE *file, short s);
+
+short readShort(FILE *file);
+
+void writeByte(FILE *file, unsigned char byte);
+
+unsigned char readByte(FILE *file);
+
+void writeString(FILE* file, const std::string &s);
+
+std::string readString(FILE *file);
+
+void writeLong(FILE* file, long num);
+
+long readLong(FILE *file);
+
#endif
diff --git a/trunk/source/food.cc b/trunk/source/food.cc
index c481186ca3..db95c533b8 100644
--- a/trunk/source/food.cc
+++ b/trunk/source/food.cc
@@ -24,6 +24,7 @@
#include "externs.h"
+#include "clua.h"
#include "debug.h"
#include "delay.h"
#include "invent.h"
@@ -42,11 +43,8 @@
#include "stuff.h"
#include "wpn-misc.h"
-static bool can_ingest(int what_isit, int kindof_thing, bool suppress_msg);
-static bool eat_from_floor(void);
static int determine_chunk_effect(int which_chunk_type, bool rotten_chunk);
static void eat_chunk( int chunk_effect );
-static void eat_from_inventory(int which_inventory_slot);
static void eating(unsigned char item_class, int item_type);
static void ghoul_eat_flesh( int chunk_effect );
static void describe_food_change(int hunger_increment);
@@ -199,10 +197,6 @@ bool butchery(void)
// be annoyed with the excess prompt).
if (Options.easy_butcher && !can_butcher)
{
- const int a_slot = letter_to_index('a');
- const int b_slot = letter_to_index('b');
- int swap_slot = a_slot;
-
//mv: check for berserk first
if (you.berserker)
{
@@ -210,27 +204,25 @@ bool butchery(void)
return (false);
}
- // Find out which slot is our auto-swap slot
- if (you.equip[EQ_WEAPON] == a_slot)
- swap_slot = b_slot;
-
-
- // check if the swap slot is appropriate first
- if (you.equip[EQ_WEAPON] != swap_slot)
+ // We'll now proceed to look through the entire inventory for
+ // choppers/slicers. We'll skip special weapons because
+ // wielding/unwielding a foo of distortion would be disastrous.
+ for (int i = 0; i < ENDOFPACK; ++i)
{
- if (is_valid_item( you.inv[ swap_slot ] ) // must have one
-
- // must be able to cut with it
- && can_cut_meat( you.inv[ swap_slot ].base_type,
- you.inv[ swap_slot ].sub_type )
-
- // must be known to be uncursed weapon
- && you.inv[ swap_slot ].base_type == OBJ_WEAPONS
- && item_known_uncursed( you.inv[ swap_slot ] ))
+ if (is_valid_item( you.inv[i] )
+ && can_cut_meat( you.inv[i].base_type,
+ you.inv[i].sub_type )
+ && you.inv[i].base_type == OBJ_WEAPONS
+ && item_known_uncursed(you.inv[i])
+ && item_ident( you.inv[i], ISFLAG_KNOW_TYPE )
+ && get_weapon_brand(you.inv[i])
+ != SPWPN_DISTORTION
+ && can_wield( you.inv[i] ))
{
- mpr( "Switching to your swap slot weapon." );
+ mpr("Switching to a butchering implement.");
wpn_switch = true;
- wield_weapon( true );
+ wield_weapon( true, i, false );
+ break;
}
}
@@ -492,42 +484,68 @@ bool butchery(void)
return (false);
} // end butchery()
-void eat_food(void)
+#ifdef CLUA_BINDINGS
+void lua_push_items(lua_State *ls, int link)
{
- int which_inventory_slot;
-
- if (you.is_undead == US_UNDEAD)
+ lua_newtable(ls);
+ int index = 0;
+ for ( ; link != NON_ITEM; link = mitm[link].link)
{
- mpr("You can't eat.");
- return;
+ lua_pushlightuserdata(ls, &mitm[link]);
+ lua_rawseti(ls, -2, ++index);
}
+}
- if (you.hunger >= 11000)
- {
- mpr("You're too full to eat anything.");
- return;
- }
+void lua_push_floor_items(lua_State *ls)
+{
+ lua_push_items(ls, igrd[you.x_pos][you.y_pos]);
+}
- if (igrd[you.x_pos][you.y_pos] != NON_ITEM)
+void lua_push_inv_items(lua_State *ls = NULL)
+{
+ if (!ls)
+ ls = clua.state();
+ lua_newtable(ls);
+ int index = 0;
+ for (unsigned slot = 0; slot < ENDOFPACK; ++slot)
{
- if (eat_from_floor())
+ if (is_valid_item(you.inv[slot]))
{
- burden_change(); // ghouls regain strength from rotten food
- return;
+ lua_pushlightuserdata(ls, &you.inv[slot]);
+ lua_rawseti(ls, -2, ++index);
}
}
+}
+#endif
+static bool userdef_eat_food()
+{
+#ifdef CLUA_BINDINGS
+ lua_push_floor_items(clua.state());
+ lua_push_inv_items();
+ bool ret = clua.callfn("c_eat", 2, 0);
+ if (!ret && clua.error.length())
+ mpr(clua.error.c_str());
+ return ret;
+#else
+ return false;
+#endif
+}
+
+bool prompt_eat_from_inventory(void)
+{
if (inv_count() < 1)
{
canned_msg(MSG_NOTHING_CARRIED);
- return;
+ return (false);
}
- which_inventory_slot = prompt_invent_item( "Eat which item?", OBJ_FOOD );
+ int which_inventory_slot =
+ prompt_invent_item( "Eat which item?", OBJ_FOOD );
if (which_inventory_slot == PROMPT_ABORT)
{
canned_msg( MSG_OK );
- return;
+ return (false);
}
// this conditional can later be merged into food::can_ingest() when
@@ -535,19 +553,53 @@ void eat_food(void)
if (you.inv[which_inventory_slot].base_type != OBJ_FOOD)
{
mpr("You can't eat that!");
- return;
+ return (false);
}
if (!can_ingest( you.inv[which_inventory_slot].base_type,
you.inv[which_inventory_slot].sub_type, false ))
{
- return;
+ return (false);
}
eat_from_inventory(which_inventory_slot);
burden_change();
you.turn_is_over = 1;
+
+ return (true);
+}
+
+// [ds] Returns true if something was eaten
+bool eat_food(bool run_hook)
+{
+ if (you.is_undead == US_UNDEAD)
+ {
+ mpr("You can't eat.");
+ return (false);
+ }
+
+ if (you.hunger >= 11000)
+ {
+ mpr("You're too full to eat anything.");
+ return (false);
+ }
+
+ // If user hook ran, we don't know whether something
+ // was eaten or not...
+ if (run_hook && userdef_eat_food())
+ return (false);
+
+ if (igrd[you.x_pos][you.y_pos] != NON_ITEM)
+ {
+ if (eat_from_floor())
+ {
+ burden_change(); // ghouls regain strength from rotten food
+ return (true);
+ }
+ }
+
+ return (prompt_eat_from_inventory());
} // end eat_food()
/*
@@ -587,6 +639,10 @@ static bool food_change(bool suppress_message)
you.hunger_state = newstate;
set_redraw_status( REDRAW_HUNGER );
+ // Stop the travel command, if it's in progress and we just got hungry
+ if (newstate < HS_SATIATED)
+ interrupt_activity( AI_HUNGRY );
+
if (suppress_message == false)
{
switch (you.hunger_state)
@@ -630,7 +686,7 @@ static void describe_food_change(int food_increment)
mpr(info);
} // end describe_food_change()
-static void eat_from_inventory(int which_inventory_slot)
+void eat_from_inventory(int which_inventory_slot)
{
if (you.inv[which_inventory_slot].sub_type == FOOD_CHUNK)
{
@@ -651,8 +707,26 @@ static void eat_from_inventory(int which_inventory_slot)
dec_inv_item_quantity( which_inventory_slot, 1 );
} // end eat_from_inventory()
+void eat_floor_item(int item_link)
+{
+ if (mitm[item_link].sub_type == FOOD_CHUNK)
+ {
+ const int chunk_type = mons_corpse_thingy( mitm[item_link].plus );
+ const bool rotten = (mitm[item_link].special < 100);
+
+ eat_chunk( determine_chunk_effect( chunk_type, rotten ) );
+ }
+ else
+ {
+ eating( mitm[item_link].base_type, mitm[item_link].sub_type );
+ }
+
+ you.turn_is_over = 1;
+
+ dec_mitm_item_quantity( item_link, 1 );
+}
-static bool eat_from_floor(void)
+bool eat_from_floor(void)
{
char str_pass[ ITEMNAME_SIZE ];
@@ -685,22 +759,7 @@ static bool eat_from_floor(void)
if (!can_ingest( mitm[o].base_type, mitm[o].sub_type, false ))
return (false);
- if (mitm[o].sub_type == FOOD_CHUNK)
- {
- const int chunk_type = mons_corpse_thingy( mitm[o].plus );
- const bool rotten = (mitm[o].special < 100);
-
- eat_chunk( determine_chunk_effect( chunk_type, rotten ) );
- }
- else
- {
- eating( mitm[o].base_type, mitm[o].sub_type );
- }
-
- you.turn_is_over = 1;
-
- dec_mitm_item_quantity( o, 1 );
-
+ eat_floor_item(o);
return (true);
}
}
@@ -1021,16 +1080,14 @@ static void eating(unsigned char item_class, int item_type)
restore_stat(STAT_ALL, false);
break;
case FOOD_PIZZA:
- strcpy(info, "Mmm... ");
-
if (SysEnv.crawl_pizza && !one_chance_in(3))
- strcat(info, SysEnv.crawl_pizza);
+ snprintf(info, INFO_SIZE, "Mmm... %s", SysEnv.crawl_pizza);
else
{
temp_rand = random2(9);
- strcat(info, (temp_rand == 0) ? "Ham and pineapple." :
- (temp_rand == 1) ? "Extra thick crust." :
+ snprintf(info, INFO_SIZE, "Mmm... %s",
+ (temp_rand == 0) ? "Ham and pineapple." :
(temp_rand == 2) ? "Vegetable." :
(temp_rand == 3) ? "Pepperoni." :
(temp_rand == 4) ? "Yeuchh - Anchovies!" :
@@ -1090,10 +1147,27 @@ static void eating(unsigned char item_class, int item_type)
return;
} // end eating()
-static bool can_ingest(int what_isit, int kindof_thing, bool suppress_msg)
+bool can_ingest(int what_isit, int kindof_thing, bool suppress_msg, bool reqid,
+ bool check_hunger)
{
bool survey_says = false;
+ // [ds] These redundant checks are now necessary - Lua might be calling us.
+ if (you.is_undead == US_UNDEAD)
+ {
+ if (!suppress_msg)
+ mpr("You can't eat.");
+ return (false);
+ }
+
+ if (check_hunger && you.hunger >= 11000)
+ {
+ if (!suppress_msg)
+ mpr("You're too full to eat anything.");
+ return (false);
+ }
+
+
bool ur_carnivorous = (you.species == SP_GHOUL
|| you.species == SP_KOBOLD
|| you.mutation[MUT_CARNIVOROUS] == 3);
@@ -1102,8 +1176,9 @@ static bool can_ingest(int what_isit, int kindof_thing, bool suppress_msg)
// ur_chunkslover not defined in terms of ur_carnivorous because
// a player could be one and not the other IMHO - 13mar2000 {dlb}
- bool ur_chunkslover = (you.hunger_state <= HS_HUNGRY
- || wearing_amulet(AMU_THE_GOURMAND)
+ bool ur_chunkslover = (
+ (check_hunger? you.hunger_state <= HS_HUNGRY : true)
+ || wearing_amulet(AMU_THE_GOURMAND, !reqid)
|| you.species == SP_KOBOLD
|| you.species == SP_OGRE
|| you.species == SP_TROLL
diff --git a/trunk/source/food.h b/trunk/source/food.h
index 49b072a54f..1c8e5f5a74 100644
--- a/trunk/source/food.h
+++ b/trunk/source/food.h
@@ -24,7 +24,7 @@ bool butchery(void);
/* ***********************************************************************
* called from: acr
* *********************************************************************** */
-void eat_food(void);
+bool eat_food(bool run_hook = true);
// last updated 19jun2000 {dlb}
@@ -54,4 +54,15 @@ void set_hunger(int new_hunger_level, bool suppress_msg);
* *********************************************************************** */
void weapon_switch( int targ );
+bool can_ingest(int what_isit, int kindof_thing, bool suppress_msg,
+ bool reqid = false, bool check_hunger = true);
+
+void eat_floor_item(int item_link);
+
+bool eat_from_floor(void);
+
+void eat_from_inventory(int which_inventory_slot);
+
+bool prompt_eat_from_inventory(void);
+
#endif
diff --git a/trunk/source/hiscores.cc b/trunk/source/hiscores.cc
index cbdab9ed2b..ab84678410 100644
--- a/trunk/source/hiscores.cc
+++ b/trunk/source/hiscores.cc
@@ -1160,6 +1160,9 @@ FILE *hs_open( const char *mode )
{
#ifdef SAVE_DIR_PATH
FILE *handle = fopen(SAVE_DIR_PATH "scores", mode);
+#ifdef SHARED_FILES_CHMOD_PUBLIC
+ chmod(SAVE_DIR_PATH "scores", SHARED_FILES_CHMOD_PUBLIC);
+#endif
#else
FILE *handle = fopen("scores", mode);
#endif
diff --git a/trunk/source/initfile.cc b/trunk/source/initfile.cc
index 706e98ea72..46bf73e40e 100644
--- a/trunk/source/initfile.cc
+++ b/trunk/source/initfile.cc
@@ -20,10 +20,16 @@
#include <string>
#include <ctype.h>
+#include "clua.h"
+#include "Kills.h"
+#include "files.h"
#include "externs.h"
#include "defines.h"
+#include "libutil.h"
#include "player.h"
+#include "stash.h"
#include "stuff.h"
+#include "travel.h"
#include "items.h"
#include "view.h"
@@ -44,25 +50,13 @@ static std::string & tolower_string( std::string &str );
const static char *obj_syms = ")([/%.?=!.+\\0}X$";
const static int obj_syms_len = 16;
-// also used with macros
-std::string & trim_string( std::string &str )
-{
- // OK, this is really annoying. Borland C++ seems to define
- // basic_string::erase to take iterators, and basic_string::remove
- // to take size_t or integer. This is ass-backwards compared to
- // nearly all other C++ compilers. Crap. (GDL)
- //
- // Borland 5.5 does this correctly now... leaving the old code
- // around for now in case anyone needs it. -- bwr
-// #ifdef __BCPLUSPLUS__
-// str.remove( 0, str.find_first_not_of( " \t\n\r" ) );
-// str.remove( str.find_last_not_of( " \t\n\r" ) + 1 );
-// #else
- str.erase( 0, str.find_first_not_of( " \t\n\r" ) );
- str.erase( str.find_last_not_of( " \t\n\r" ) + 1 );
-// #endif
+static void read_startup_prefs();
+static void read_options(InitLineInput &il, bool runscript);
- return (str);
+template<class A, class B> void append_vector(
+ std::vector<A> &dest, const std::vector<B> &src)
+{
+ dest.insert( dest.end(), src.begin(), src.end() );
}
// returns -1 if unmatched else returns 0-15
@@ -92,11 +86,21 @@ static short str_to_colour( const std::string &str )
ret = 8;
}
+ if (ret == 16)
+ {
+ // Check if we have a direct colour index
+ const char *s = str.c_str();
+ char *es = NULL;
+ int ci = (int) strtol(s, &es, 10);
+ if (s != (const char *) es && es && ci >= 0 && ci < 16)
+ ret = ci;
+ }
+
return ((ret == 16) ? -1 : ret);
}
// returns -1 if unmatched else returns 0-15
-static short str_to_channel_colour( const std::string &str )
+static int str_to_channel_colour( const std::string &str )
{
int ret = str_to_colour( str );
@@ -115,28 +119,36 @@ static short str_to_channel_colour( const std::string &str )
return (ret);
}
+static const std::string message_channel_names[ NUM_MESSAGE_CHANNELS ] =
+{
+ "plain", "prompt", "god", "duration", "danger", "warning", "food",
+ "recovery", "talk", "intrinsic_gain", "mutation", "monster_spell",
+ "monster_enchant", "monster_damage", "rotten_meat", "equipment",
+ "diagnostic",
+};
+
// returns -1 if unmatched else returns 0-15
-static short str_to_channel( const std::string &str )
+int str_to_channel( const std::string &str )
{
short ret;
- const std::string cols[ NUM_MESSAGE_CHANNELS ] =
- {
- "plain", "prompt", "god", "duration", "danger", "warning", "food",
- "recovery", "talk", "intrinsic_gain", "mutation", "monster_spell",
- "monster_enchant", "monster_damage", "rotten_meat", "equipment",
- "diagnostic",
- };
-
for (ret = 0; ret < NUM_MESSAGE_CHANNELS; ret++)
{
- if (str == cols[ret])
+ if (str == message_channel_names[ret])
break;
}
return (ret == NUM_MESSAGE_CHANNELS ? -1 : ret);
}
+std::string channel_to_str( int channel )
+{
+ if (channel < 0 || channel >= NUM_MESSAGE_CHANNELS)
+ return "";
+
+ return message_channel_names[channel];
+}
+
static int str_to_weapon( const std::string &str )
{
if (str == "shortsword" || str == "short sword")
@@ -155,6 +167,27 @@ static int str_to_weapon( const std::string &str )
return (WPN_UNKNOWN);
}
+static std::string weapon_to_str( int weapon )
+{
+ switch (weapon)
+ {
+ case WPN_SHORT_SWORD:
+ return "short sword";
+ case WPN_MACE:
+ return "mace";
+ case WPN_SPEAR:
+ return "spear";
+ case WPN_TRIDENT:
+ return "trident";
+ case WPN_HAND_AXE:
+ return "hand axe";
+ case WPN_RANDOM:
+ return "random";
+ default:
+ return "random";
+ }
+}
+
static unsigned int str_to_fire_types( const std::string &str )
{
if (str == "launcher")
@@ -261,13 +294,72 @@ static bool read_bool( const std::string &field, bool def_value )
return (ret);
}
-void read_init_file(void)
+static unsigned curses_attribute(const std::string &field)
+{
+ if (field == "standout") // probably reverses
+ return CHATTR_STANDOUT;
+ else if (field == "bold") // probably brightens fg
+ return CHATTR_BOLD;
+ else if (field == "blink") // probably brightens bg
+ return CHATTR_BLINK;
+ else if (field == "underline")
+ return CHATTR_UNDERLINE;
+ else if (field == "reverse")
+ return CHATTR_REVERSE;
+ else if (field == "dim")
+ return CHATTR_DIM;
+ else if (field.find("hi:") == 0 || field.find("hilite:") == 0 ||
+ field.find("highlight:") == 0)
+ {
+ int col = field.find(":");
+ int colour = str_to_colour(field.substr(col + 1));
+ if (colour == -1)
+ fprintf(stderr, "Bad highlight string -- %s\n", field.c_str());
+ else
+ return CHATTR_HILITE | (colour << 8);
+ }
+ else
+ fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
+ return CHATTR_NORMAL;
+}
+
+
+static void reset_startup_options(bool clear_name = true)
{
- unsigned int i;
+ if (clear_name)
+ you.your_name[0] = '\0';
+ Options.race = '\0';
+ Options.cls = '\0';
+ Options.weapon = WPN_UNKNOWN;
+ Options.random_pick = false;
+ Options.chaos_knight = GOD_NO_GOD;
+ Options.death_knight = DK_NO_SELECTION;
+ Options.priest = GOD_NO_GOD;
+}
+void reset_options(bool clear_name)
+{
// Option initialization
+ Options.activity_interrupts[ ACT_MULTIDROP ] =
+ AI_HP_LOSS | AI_STAT_CHANGE | AI_TELEPORT;
+ Options.activity_interrupts[ ACT_RUNNING ] =
+ 0xFFFF ^ AI_SEE_MONSTER;
+ Options.activity_interrupts[ ACT_TRAVELING ] =
+ 0xFFFF ^ (AI_MESSAGE | AI_SEE_MONSTER);
+
+ reset_startup_options(clear_name);
+ Options.prev_race = 0;
+ Options.prev_cls = 0;
+ Options.prev_ck = GOD_NO_GOD;
+ Options.prev_dk = DK_NO_SELECTION;
+ Options.prev_pr = GOD_NO_GOD;
+ Options.prev_weapon = WPN_UNKNOWN;
+ Options.prev_randpick = false;
+ Options.remember_name = false;
+
Options.autopickups = 0x0000;
Options.verbose_dump = false;
+ Options.detailed_stat_dump = true;
Options.colour_map = false;
Options.clean_map = false;
Options.show_uncursed = true;
@@ -277,22 +369,51 @@ void read_init_file(void)
Options.easy_butcher = false;
Options.easy_confirm = CONFIRM_SAFE_EASY;
Options.easy_quit_item_prompts = false;
- Options.weapon = WPN_UNKNOWN;
- Options.random_pick = false;
- Options.chaos_knight = GOD_NO_GOD;
- Options.death_knight = DK_NO_SELECTION;
- Options.priest = GOD_NO_GOD;
Options.hp_warning = 10;
Options.hp_attention = 25;
- Options.race = '\0';
- Options.cls = '\0';
Options.terse_hand = true;
Options.auto_list = false;
Options.delay_message_clear = false;
+ Options.pickup_dropped = true;
+ Options.travel_colour = true;
+ Options.travel_delay = -1;
+ Options.travel_stair_cost = 500;
+ Options.travel_exclude_radius2 = 68;
+ Options.show_waypoints = true;
+ Options.item_colour = false;
+
+ Options.detected_item_colour = DARKGREY;
+ Options.detected_monster_colour= DARKGREY;
+ Options.remembered_monster_colour = 0;
+
+ Options.easy_exit_menu = true;
+ Options.dos_use_background_intensity = false;
+ Options.assign_item_slot = SS_FORWARD;
+
+ Options.macro_meta_entry = true;
+
+ // 10 was the cursor step default on Linux.
+ Options.level_map_cursor_step = 10;
+
+#ifdef STASH_TRACKING
+ Options.stash_tracking = STM_NONE;
+#endif
+ Options.explore_stop = ES_ITEM | ES_STAIR | ES_SHOP | ES_ALTAR;
+ Options.target_zero_exp = true;
+ Options.target_wrap = false;
+ Options.target_oos = true;
+ Options.target_los_first = true;
+ Options.dump_kill_places = KDO_ONE_PLACE;
+ Options.dump_message_count = 4;
+ Options.dump_item_origins = IODS_ARTIFACTS | IODS_RODS;
+ Options.dump_item_origin_price = -1;
+
+ Options.drop_mode = DM_SINGLE;
Options.flush_input[ FLUSH_ON_FAILURE ] = true;
Options.flush_input[ FLUSH_BEFORE_COMMAND ] = false;
Options.flush_input[ FLUSH_ON_MESSAGE ] = false;
+ Options.flush_input[ FLUSH_LUA ] = true;
Options.lowercase_invocations = false;
@@ -303,7 +424,7 @@ void read_init_file(void)
Options.fire_order[1] = FIRE_DART; // then only consider darts
// clear the reast of the list
- for (i = 2; i < NUM_FIRE_TYPES; i++)
+ for (int i = 2; i < NUM_FIRE_TYPES; i++)
Options.fire_order[i] = FIRE_NONE;
// These are only used internally, and only from the commandline:
@@ -311,10 +432,13 @@ void read_init_file(void)
Options.sc_entries = 0;
Options.sc_format = SCORE_REGULAR;
-#ifdef USE_COLOUR_OPTS
- Options.friend_brand = CHATTR_NORMAL;
+//#ifdef USE_COLOUR_OPTS
+ Options.friend_brand = CHATTR_NORMAL;
+ Options.heap_brand = CHATTR_NORMAL;
+ Options.stab_brand = CHATTR_NORMAL;
+ Options.may_stab_brand = CHATTR_NORMAL;
Options.no_dark_brand = 0;
-#endif
+//#endif
#ifdef WIZARD
Options.wiz_mode = WIZ_NO;
@@ -322,26 +446,46 @@ void read_init_file(void)
// map each colour to itself as default
#ifdef USE_8_COLOUR_TERM_MAP
- for (i = 0; i < 16; i++)
+ for (int i = 0; i < 16; i++)
Options.colour[i] = i % 8;
Options.colour[ DARKGREY ] = COL_TO_REPLACE_DARKGREY;
#else
- for (i = 0; i < 16; i++)
+ for (int i = 0; i < 16; i++)
Options.colour[i] = i;
#endif
-
+
// map each channel to plain (well, default for now since I'm testing)
for (int i = 0; i < NUM_MESSAGE_CHANNELS; i++)
Options.channels[i] = MSGCOL_DEFAULT;
+ // Clear vector options.
+ Options.banned_objects.clear();
+ Options.stop_travel.clear();
+ Options.sound_mappings.clear();
+ Options.menu_colour_mappings.clear();
+ Options.drop_filter.clear();
+
+ Options.named_options.clear();
+
+ // Map each category to itself. The user can override in init.txt
+ Options.kill_map[KC_YOU] = KC_YOU;
+ Options.kill_map[KC_FRIENDLY] = KC_FRIENDLY;
+ Options.kill_map[KC_OTHER] = KC_OTHER;
+
+ // Setup travel information. What's a better place to do this?
+ initialise_travel();
+}
+
+void read_init_file(bool runscript)
+{
FILE *f;
- char s[255];
- unsigned int line = 0;
- int j;
char name_buff[kPathLen];
- you.your_name[0] = '\0';
+ reset_options(!runscript);
+
+ if (!runscript)
+ you.your_name[0] = '\0';
if (SysEnv.crawl_rc)
{
@@ -379,380 +523,977 @@ void read_init_file(void)
if (f == NULL)
return;
- while (!feof(f))
+ if (!runscript)
+ read_startup_prefs();
+
+ read_options(f, runscript);
+ fclose(f);
+} // end read_init_file()
+
+static void read_startup_prefs()
+{
+ std::string fn = get_prefs_filename();
+ FILE *f = fopen(fn.c_str(), "r");
+ if (!f)
+ return;
+ read_options(f);
+ fclose(f);
+
+ Options.prev_randpick = Options.random_pick;
+ Options.prev_weapon = Options.weapon;
+ Options.prev_pr = Options.priest;
+ Options.prev_dk = Options.death_knight;
+ Options.prev_ck = Options.chaos_knight;
+ Options.prev_cls = Options.cls;
+ Options.prev_race = Options.race;
+ Options.prev_name = you.your_name;
+
+ reset_startup_options();
+}
+
+static void write_newgame_options(FILE *f)
+{
+ // Write current player name
+ fprintf(f, "name = %s\n", you.your_name);
+
+ if (Options.prev_randpick)
+ Options.prev_race = Options.prev_cls = '?';
+
+ // Race selection
+ if (Options.prev_race)
+ fprintf(f, "race = %c\n", Options.prev_race);
+ if (Options.prev_cls)
+ fprintf(f, "class = %c\n", Options.prev_cls);
+
+ if (Options.prev_weapon != WPN_UNKNOWN)
+ fprintf(f, "weapon = %s\n", weapon_to_str(Options.prev_weapon).c_str());
+
+ if (Options.prev_ck != GOD_NO_GOD)
+ {
+ fprintf(f, "chaos_knight = %s\n",
+ Options.prev_ck == GOD_XOM? "xom" :
+ Options.prev_ck == GOD_MAKHLEB? "makhleb" :
+ "random");
+ }
+ if (Options.prev_dk != DK_NO_SELECTION)
{
- fgets(s, 255, f);
+ fprintf(f, "death_knight = %s\n",
+ Options.prev_dk == DK_NECROMANCY? "necromancy" :
+ Options.prev_dk == DK_YREDELEMNUL? "yredelemnul" :
+ "random");
+ }
+ if (Options.prev_pr != GOD_NO_GOD)
+ {
+ fprintf(f, "priest = %s\n",
+ Options.prev_pr == GOD_ZIN? "zin" :
+ Options.prev_pr == GOD_YREDELEMNUL? "yredelemnul" :
+ "random");
+ }
+}
- line++;
+void write_newgame_options_file()
+{
+ std::string fn = get_prefs_filename();
+ FILE *f = fopen(fn.c_str(), "w");
+ if (!f)
+ return;
+ write_newgame_options(f);
+ fclose(f);
+}
- std::string str = s;
- trim_string( str );
+void save_player_name()
+{
+ if (!Options.remember_name)
+ return ;
- // This is to make some efficient comments
- if (s[0] == '#' || s[0] == '\0')
- continue;
+ std::string playername = you.your_name;
- std::string key = "";
- std::string subkey = "";
- std::string field = "";
+ // Read other preferences
+ read_startup_prefs();
- int first_equals = str.find('=');
- int first_dot = str.find('.');
+ // Put back your name
+ strncpy(you.your_name, playername.c_str(), kNameLen);
+ you.your_name[kNameLen - 1] = 0;
- // all lines with no equal-signs we ignore
- if (first_equals < 0)
- continue;
+ // And save
+ write_newgame_options_file();
+}
+
+void read_options(FILE *f, bool runscript)
+{
+ FileLineInput fl(f);
+ read_options(fl, runscript);
+}
- if (first_dot > 0 && first_dot < first_equals)
- {
- key = str.substr( 0, first_dot );
- subkey = str.substr( first_dot + 1, first_equals - first_dot - 1 );
- field = str.substr( first_equals + 1 );
- }
- else
- {
- // no subkey (dots are okay in value field)
- key = str.substr( 0, first_equals );
- subkey = "";
- field = str.substr( first_equals + 1 );
- }
+void read_options(const std::string &s, bool runscript)
+{
+ StringLineInput st(s);
+ read_options(st, runscript);
+}
- // Clean up our data...
- tolower_string( trim_string( key ) );
- tolower_string( trim_string( subkey ) );
+static void read_options(InitLineInput &il, bool runscript)
+{
+ unsigned int line = 0;
+
+ bool inscriptblock = false;
+ bool inscriptcond = false;
+ bool isconditional = false;
- // some fields want capitals... none care about external spaces
- trim_string( field );
- if (key != "name" && key != "crawl_dir"
- && key != "race" && key != "class")
- {
- tolower_string( field );
- }
+ bool l_init = false;
- // everything not a valid line is treated as a comment
- if (key == "autopickup")
- {
- for (i = 0; i < field.length(); i++)
- {
- char type = field[i];
+ std::string luacond;
+ std::string luacode;
+ while (!il.eof())
+ {
+ std::string s = il.getline();
+ std::string str = s;
+ line++;
- // Make the amulet symbol equiv to ring -- bwross
- switch (type)
- {
- case '"':
- // also represents jewellery
- type = '=';
- break;
-
- case '|':
- // also represents staves
- type = '\\';
- break;
-
- case ':':
- // also represents books
- type = '+';
- break;
-
- case 'x':
- // also corpses
- type = 'X';
- break;
- }
+ trim_string( str );
- for (j = 0; j < obj_syms_len && type != obj_syms[j]; j++)
- ;
+ // This is to make some efficient comments
+ if ((str.empty() || str[0] == '#') && !inscriptcond && !inscriptblock)
+ continue;
+
+ if (!inscriptcond && (str.find("L<") == 0 || str.find("<") == 0))
+ {
+ // The init file is now forced into isconditional mode.
+ isconditional = true;
+ inscriptcond = true;
+
+ str = str.substr( str.find("L<") == 0? 2 : 1 );
+ // Is this a one-liner?
+ if (!str.empty() && str[ str.length() - 1 ] == '>') {
+ inscriptcond = false;
+ str = str.substr(0, str.length() - 1);
+ }
- if (j < obj_syms_len)
- Options.autopickups |= (1L << j);
- else
+ if (!str.empty() && runscript)
+ {
+ // If we're in the middle of an option block, close it.
+ if (luacond.length() && l_init)
{
- fprintf( stderr, "Bad object type '%c' for autopickup.\n",
- type );
+ luacond += "]] )\n";
+ l_init = false;
}
+ luacond += str + "\n";
}
+ continue;
}
- else if (key == "name")
- {
- // field is already cleaned up from trim_string()
- strncpy(you.your_name, field.c_str(), kNameLen);
- you.your_name[ kNameLen - 1 ] = '\0';
- }
- else if (key == "verbose_dump")
- {
- // gives verbose info in char dumps
- Options.verbose_dump = read_bool( field, Options.verbose_dump );
- }
- else if (key == "clean_map")
+ else if (inscriptcond &&
+ (str.find(">") == str.length() - 1 || str == ">L"))
{
- // removes monsters/clouds from map
- Options.clean_map = read_bool( field, Options.clean_map );
+ inscriptcond = false;
+ str = str.substr(0, str.length() - 1);
+ if (!str.empty() && runscript)
+ luacond += str + "\n";
+ continue;
}
- else if (key == "colour_map" || key == "color_map")
+ else if (inscriptcond)
{
- // colour-codes play-screen map
- Options.colour_map = read_bool( field, Options.colour_map );
+ if (runscript)
+ luacond += s + "\n";
+ continue;
}
- else if (key == "easy_confirm")
+
+ // Handle blocks of Lua
+ if (!inscriptblock && (str.find("Lua{") == 0 || str.find("{") == 0))
{
- // allows both 'Y'/'N' and 'y'/'n' on yesno() prompts
- if (field == "none")
- Options.easy_confirm = CONFIRM_NONE_EASY;
- else if (field == "safe")
- Options.easy_confirm = CONFIRM_SAFE_EASY;
- else if (field == "all")
- Options.easy_confirm = CONFIRM_ALL_EASY;
+ inscriptblock = true;
+ luacode.clear();
+
+ // Strip leading Lua[
+ str = str.substr( str.find("Lua{") == 0? 4 : 1 );
+
+ if (!str.empty() && str.find("}") == str.length() - 1)
+ {
+ str = str.substr(0, str.length() - 1);
+ inscriptblock = false;
+ }
+
+ if (!str.empty())
+ luacode += str + "\n";
+
+ if (!inscriptblock && runscript)
+ {
+#ifdef CLUA_BINDINGS
+ clua.execstring(luacode.c_str());
+ if (clua.error.length())
+ fprintf(stderr, "Lua error: %s\n", clua.error.c_str());
+ luacode.clear();
+#endif
+ }
+
+ continue;
}
- else if (key == "easy_quit_item_lists")
+ else if (inscriptblock && (str == "}Lua" || str == "}"))
{
- // allow aborting of item lists with space
- Options.easy_quit_item_prompts = read_bool( field,
- Options.easy_quit_item_prompts );
+ inscriptblock = false;
+#ifdef CLUA_BINDINGS
+ if (runscript)
+ {
+ clua.execstring(luacode.c_str());
+ if (clua.error.length())
+ fprintf(stderr, "Lua error: %s\n",
+ clua.error.c_str());
+ }
+#endif
+ luacode.clear();
+ continue;
}
- else if (key == "easy_open")
+ else if (inscriptblock)
{
- // automatic door opening with movement
- Options.easy_open = read_bool( field, Options.easy_open );
+ luacode += s + "\n";
+ continue;
}
- else if (key == "easy_armour" || key == "easy_armour")
+
+ if (isconditional && runscript)
{
- // automatic removal of armour when dropping
- Options.easy_armour = read_bool( field, Options.easy_armour );
+ if (!l_init)
+ {
+ luacond += "crawl.setopt( [[\n";
+ l_init = true;
+ }
+
+ luacond += s + "\n";
+ continue;
}
- else if (key == "easy_butcher")
+
+ parse_option_line(str, runscript);
+ }
+
+#ifdef CLUA_BINDINGS
+ if (runscript && !luacond.empty())
+ {
+ if (l_init)
+ luacond += "]] )\n";
+ clua.execstring(luacond.c_str());
+ if (clua.error.length())
{
- // automatic knife switching
- Options.easy_butcher = read_bool( field, Options.easy_butcher );
+ mpr( ("Lua error: " + clua.error).c_str() );
}
- else if (key == "colour" || key == "color")
+ }
+#endif
+}
+
+static int str_to_killcategory(const std::string &s)
+{
+ static const char *kc[] = {
+ "you",
+ "friend",
+ "other",
+ };
+
+ for (unsigned i = 0; i < sizeof(kc) / sizeof(*kc); ++i) {
+ if (s == kc[i])
+ return i;
+ }
+ return -1;
+}
+
+static void do_kill_map(const std::string &from, const std::string &to)
+{
+ int ifrom = str_to_killcategory(from),
+ ito = str_to_killcategory(to);
+ if (ifrom != -1 && ito != -1)
+ Options.kill_map[ifrom] = ito;
+}
+
+void parse_option_line(const std::string &str, bool runscript)
+{
+ std::string key = "";
+ std::string subkey = "";
+ std::string field = "";
+
+ int first_equals = str.find('=');
+ int first_dot = str.find('.');
+
+ // all lines with no equal-signs we ignore
+ if (first_equals < 0)
+ return;
+
+ if (first_dot > 0 && first_dot < first_equals)
+ {
+ key = str.substr( 0, first_dot );
+ subkey = str.substr( first_dot + 1, first_equals - first_dot - 1 );
+ field = str.substr( first_equals + 1 );
+ }
+ else
+ {
+ // no subkey (dots are okay in value field)
+ key = str.substr( 0, first_equals );
+ subkey = "";
+ field = str.substr( first_equals + 1 );
+ }
+
+ // Clean up our data...
+ tolower_string( trim_string( key ) );
+ tolower_string( trim_string( subkey ) );
+
+ // some fields want capitals... none care about external spaces
+ trim_string( field );
+
+ // Keep unlowercased field around
+ std::string orig_field = field;
+
+ if (key != "name" && key != "crawl_dir"
+ && key != "race" && key != "class" && key != "ban_pickup"
+ && key != "stop_travel" && key != "sound"
+ && key != "drop_filter" && key != "lua_file")
+ {
+ tolower_string( field );
+ }
+
+ // everything not a valid line is treated as a comment
+ if (key == "autopickup")
+ {
+ for (size_t i = 0; i < field.length(); i++)
{
- const int orig_col = str_to_colour( subkey );
- const int result_col = str_to_colour( field );
+ char type = field[i];
+
+ // Make the amulet symbol equiv to ring -- bwross
+ switch (type)
+ {
+ case '"':
+ // also represents jewellery
+ type = '=';
+ break;
- if (orig_col != -1 && result_col != -1)
- Options.colour[orig_col] = result_col;
+ case '|':
+ // also represents staves
+ type = '\\';
+ break;
+
+ case ':':
+ // also represents books
+ type = '+';
+ break;
+
+ case 'x':
+ // also corpses
+ type = 'X';
+ break;
+ }
+
+ int j;
+ for (j = 0; j < obj_syms_len && type != obj_syms[j]; j++)
+ ;
+
+ if (j < obj_syms_len)
+ Options.autopickups |= (1L << j);
else
{
- fprintf( stderr, "Bad colour -- %s=%d or %s=%d\n",
- subkey.c_str(), orig_col, field.c_str(), result_col );
+ fprintf( stderr, "Bad object type '%c' for autopickup.\n",
+ type );
}
}
- else if (key == "channel")
+ }
+ else if (key == "name")
+ {
+ // field is already cleaned up from trim_string()
+ strncpy(you.your_name, field.c_str(), kNameLen);
+ you.your_name[ kNameLen - 1 ] = '\0';
+ }
+ else if (key == "verbose_dump")
+ {
+ // gives verbose info in char dumps
+ Options.verbose_dump = read_bool( field, Options.verbose_dump );
+ }
+ else if (key == "detailed_stat_dump")
+ {
+ Options.detailed_stat_dump =
+ read_bool( field, Options.detailed_stat_dump );
+ }
+ else if (key == "clean_map")
+ {
+ // removes monsters/clouds from map
+ Options.clean_map = read_bool( field, Options.clean_map );
+ }
+ else if (key == "colour_map" || key == "color_map")
+ {
+ // colour-codes play-screen map
+ Options.colour_map = read_bool( field, Options.colour_map );
+ }
+ else if (key == "easy_confirm")
+ {
+ // allows both 'Y'/'N' and 'y'/'n' on yesno() prompts
+ if (field == "none")
+ Options.easy_confirm = CONFIRM_NONE_EASY;
+ else if (field == "safe")
+ Options.easy_confirm = CONFIRM_SAFE_EASY;
+ else if (field == "all")
+ Options.easy_confirm = CONFIRM_ALL_EASY;
+ }
+ else if (key == "easy_quit_item_lists")
+ {
+ // allow aborting of item lists with space
+ Options.easy_quit_item_prompts = read_bool( field,
+ Options.easy_quit_item_prompts );
+ }
+ else if (key == "easy_open")
+ {
+ // automatic door opening with movement
+ Options.easy_open = read_bool( field, Options.easy_open );
+ }
+ else if (key == "easy_armour" || key == "easy_armour")
+ {
+ // automatic removal of armour when dropping
+ Options.easy_armour = read_bool( field, Options.easy_armour );
+ }
+ else if (key == "easy_butcher")
+ {
+ // automatic knife switching
+ Options.easy_butcher = read_bool( field, Options.easy_butcher );
+ }
+ 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());
+#endif
+ }
+ else if (key == "colour" || key == "color")
+ {
+ const int orig_col = str_to_colour( subkey );
+ const int result_col = str_to_colour( field );
+
+ if (orig_col != -1 && result_col != -1)
+ Options.colour[orig_col] = result_col;
+ else
{
- const int chnl = str_to_channel( subkey );
- const int col = str_to_channel_colour( field );
-
- if (chnl != -1 && col != -1)
- Options.channels[chnl] = col;
- else if (chnl == -1)
- fprintf( stderr, "Bad channel -- %s\n", subkey.c_str() );
- else if (col == -1)
- fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
+ fprintf( stderr, "Bad colour -- %s=%d or %s=%d\n",
+ subkey.c_str(), orig_col, field.c_str(), result_col );
}
- else if (key == "background")
- {
- // change background colour
- // Experimental! This may look really bad!
- const int col = str_to_colour( field );
+ }
+ else if (key == "channel")
+ {
+ const int chnl = str_to_channel( subkey );
+ const int col = str_to_channel_colour( field );
+
+ if (chnl != -1 && col != -1)
+ Options.channels[chnl] = col;
+ else if (chnl == -1)
+ fprintf( stderr, "Bad channel -- %s\n", subkey.c_str() );
+ else if (col == -1)
+ fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
+ }
+ else if (key == "background")
+ {
+ // change background colour
+ // Experimental! This may look really bad!
+ const int col = str_to_colour( field );
+ if (col != -1)
+ Options.background = col;
+ else
+ fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
+
+ }
+ else if (key == "detected_item_colour")
+ {
+ const int col = str_to_colour( field );
+ if (col != -1)
+ Options.detected_item_colour = col;
+ else
+ fprintf( stderr, "Bad detected_item_colour -- %s\n",
+ field.c_str());
+ }
+ else if (key == "detected_monster_colour")
+ {
+ const int col = str_to_colour( field );
+ if (col != -1)
+ Options.detected_monster_colour = col;
+ else
+ fprintf( stderr, "Bad detected_monster_colour -- %s\n",
+ field.c_str());
+ }
+ else if (key == "remembered_monster_colour")
+ {
+ if (field == "real")
+ Options.remembered_monster_colour = 0xFFFFU;
+ else if (field == "auto")
+ Options.remembered_monster_colour = 0;
+ else {
+ const int col = str_to_colour( field );
if (col != -1)
- Options.background = col;
+ Options.remembered_monster_colour = col;
else
- fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
-
+ fprintf( stderr, "Bad remembered_monster_colour -- %s\n",
+ field.c_str());
}
-#ifdef USE_COLOUR_OPTS
- else if (key == "friend_brand")
+ }
+ else if (key == "friend_brand")
+ {
+ // Use curses attributes to mark friend
+ // Some may look bad on some terminals.
+ // As a suggestion, try "rxvt -rv -fn 10x20" under Un*xes
+ Options.friend_brand = curses_attribute(field);
+ }
+ else if (key == "stab_brand")
+ {
+ Options.stab_brand = curses_attribute(field);
+ }
+ else if (key == "may_stab_brand")
+ {
+ Options.may_stab_brand = curses_attribute(field);
+ }
+ else if (key == "no_dark_brand")
+ {
+ // This is useful for terms where dark grey does
+ // not have standout modes (since it's black on black).
+ // This option will use light-grey instead in these cases.
+ Options.no_dark_brand = read_bool( field, Options.no_dark_brand );
+ }
+ else if (key == "heap_brand")
+ {
+ // See friend_brand option upstairs. no_dark_brand applies
+ // here as well.
+ Options.heap_brand = curses_attribute(field);
+ }
+ else if (key == "show_uncursed")
+ {
+ // label known uncursed items as "uncursed"
+ Options.show_uncursed = read_bool( field, Options.show_uncursed );
+ }
+ else if (key == "always_greet")
+ {
+ // show greeting when reloading game
+ Options.always_greet = read_bool( field, Options.always_greet );
+ }
+ else if (key == "weapon")
+ {
+ // choose this weapon for classes that get choice
+ Options.weapon = str_to_weapon( field );
+ }
+ else if (key == "chaos_knight")
+ {
+ // choose god for Chaos Knights
+ if (field == "xom")
+ Options.chaos_knight = GOD_XOM;
+ else if (field == "makhleb")
+ Options.chaos_knight = GOD_MAKHLEB;
+ else if (field == "random")
+ Options.chaos_knight = GOD_RANDOM;
+ }
+ else if (key == "death_knight")
+ {
+ if (field == "necromancy")
+ Options.death_knight = DK_NECROMANCY;
+ else if (field == "yredelemnul")
+ Options.death_knight = DK_YREDELEMNUL;
+ else if (field == "random")
+ Options.death_knight = DK_RANDOM;
+ }
+ else if (key == "priest")
+ {
+ // choose this weapon for classes that get choice
+ if (field == "zin")
+ Options.priest = GOD_ZIN;
+ else if (field == "yredelemnul")
+ Options.priest = GOD_YREDELEMNUL;
+ else if (field == "random")
+ Options.priest = GOD_RANDOM;
+ }
+ else if (key == "fire_items_start")
+ {
+ if (isalpha( field[0] ))
+ Options.fire_items_start = letter_to_index( field[0] );
+ else
{
- // Use curses attributes to mark friend
- // Some may look bad on some terminals.
- // As a suggestion, try "rxvt -rv -fn 10x20" under Un*xes
- if (field == "standout") // probably reverses
- Options.friend_brand = CHATTR_STANDOUT;
- else if (field == "bold") // probably brightens fg
- Options.friend_brand = CHATTR_BOLD;
- else if (field == "blink") // probably brightens bg
- Options.friend_brand = CHATTR_BLINK;
- else if (field == "underline")
- Options.friend_brand = CHATTR_UNDERLINE;
- else if (field == "reverse")
- Options.friend_brand = CHATTR_REVERSE;
- else if (field == "dim")
- Options.friend_brand = CHATTR_DIM;
- else
- fprintf( stderr, "Bad colour -- %s\n", field.c_str() );
+ fprintf( stderr, "Bad fire item start index -- %s\n",
+ field.c_str() );
}
- else if (key == "no_dark_brand")
+ }
+ else if (key == "assign_item_slot")
+ {
+ if (field == "forward")
+ Options.assign_item_slot = SS_FORWARD;
+ else if (field == "backward")
+ Options.assign_item_slot = SS_BACKWARD;
+ }
+ else if (key == "fire_order")
+ {
+ str_to_fire_order( field, Options.fire_order );
+ }
+ else if (key == "random_pick")
+ {
+ // randomly generate character
+ Options.random_pick = read_bool( field, Options.random_pick );
+ }
+ else if (key == "remember_name")
+ {
+ Options.remember_name = read_bool( field, Options.remember_name );
+ }
+ else if (key == "hp_warning")
+ {
+ Options.hp_warning = atoi( field.c_str() );
+ if (Options.hp_warning < 0 || Options.hp_warning > 100)
{
- // This is useful for terms where dark grey does
- // not have standout modes (since it's black on black).
- // This option will use light-grey instead in these cases.
- Options.no_dark_brand = read_bool( field, Options.no_dark_brand );
+ Options.hp_warning = 0;
+ fprintf( stderr, "Bad HP warning percentage -- %s\n",
+ field.c_str() );
}
-#endif
- else if (key == "show_uncursed")
+ }
+ else if (key == "hp_attention")
+ {
+ Options.hp_attention = atoi( field.c_str() );
+ if (Options.hp_attention < 0 || Options.hp_attention > 100)
{
- // label known uncursed items as "uncursed"
- Options.show_uncursed = read_bool( field, Options.show_uncursed );
+ Options.hp_attention = 0;
+ fprintf( stderr, "Bad HP attention percentage -- %s\n",
+ field.c_str() );
}
- else if (key == "always_greet")
+ }
+ else if (key == "crawl_dir")
+ {
+ // 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)
{
- // show greeting when reloading game
- Options.always_greet = read_bool( field, Options.always_greet );
+ strncpy(SysEnv.crawl_dir, field.c_str(), kNameLen - 1);
+ SysEnv.crawl_dir[ kNameLen - 1 ] = '\0';
}
- else if (key == "weapon")
+ }
+ else if (key == "race")
+ {
+ Options.race = str_to_race( field );
+
+ if (Options.race == '\0')
+ fprintf( stderr, "Unknown race choice: %s\n", field.c_str() );
+ }
+ else if (key == "class")
+ {
+ Options.cls = str_to_class( field );
+
+ if (Options.cls == '\0')
+ fprintf( stderr, "Unknown class choice: %s\n", field.c_str() );
+ }
+ else if (key == "auto_list")
+ {
+ Options.auto_list = read_bool( field, Options.auto_list );
+ }
+ else if (key == "delay_message_clear")
+ {
+ Options.delay_message_clear = read_bool( field, Options.delay_message_clear );
+ }
+ else if (key == "terse_hand")
+ {
+ Options.terse_hand = read_bool( field, Options.terse_hand );
+ }
+ else if (key == "flush")
+ {
+ if (subkey == "failure")
{
- // choose this weapon for classes that get choice
- Options.weapon = str_to_weapon( field );
+ Options.flush_input[FLUSH_ON_FAILURE]
+ = read_bool(field, Options.flush_input[FLUSH_ON_FAILURE]);
}
- else if (key == "chaos_knight")
+ else if (subkey == "command")
{
- // choose god for Chaos Knights
- if (field == "xom")
- Options.chaos_knight = GOD_XOM;
- else if (field == "makhleb")
- Options.chaos_knight = GOD_MAKHLEB;
- else if (field == "random")
- Options.chaos_knight = GOD_RANDOM;
+ Options.flush_input[FLUSH_BEFORE_COMMAND]
+ = read_bool(field, Options.flush_input[FLUSH_BEFORE_COMMAND]);
}
- else if (key == "death_knight")
+ else if (subkey == "message")
{
- if (field == "necromancy")
- Options.death_knight = DK_NECROMANCY;
- else if (field == "yredelemnul")
- Options.death_knight = DK_YREDELEMNUL;
- else if (field == "random")
- Options.death_knight = DK_RANDOM;
+ Options.flush_input[FLUSH_ON_MESSAGE]
+ = read_bool(field, Options.flush_input[FLUSH_ON_MESSAGE]);
}
- else if (key == "priest")
+ else if (subkey == "lua")
{
- // choose this weapon for classes that get choice
- if (field == "zin")
- Options.priest = GOD_ZIN;
- else if (field == "yredelemnul")
- Options.priest = GOD_YREDELEMNUL;
- else if (field == "random")
- Options.priest = GOD_RANDOM;
+ Options.flush_input[FLUSH_LUA]
+ = read_bool(field, Options.flush_input[FLUSH_LUA]);
}
- else if (key == "fire_items_start")
- {
- if (isalpha( field[0] ))
- Options.fire_items_start = letter_to_index( field[0] );
- else
- {
- fprintf( stderr, "Bad fire item start index -- %s\n",
- field.c_str() );
+ }
+ else if (key == "lowercase_invocations")
+ {
+ Options.lowercase_invocations
+ = read_bool(field, Options.lowercase_invocations);
+ }
+ else if (key == "wiz_mode")
+ {
+ // wiz_mode is recognized as a legal key in all compiles -- bwr
+#ifdef WIZARD
+ if (field == "never")
+ Options.wiz_mode = WIZ_NEVER;
+ else if (field == "no")
+ Options.wiz_mode = WIZ_NO;
+ else if (field == "yes")
+ Options.wiz_mode = WIZ_YES;
+ else
+ fprintf(stderr, "Unknown wiz_mode option: %s\n", field.c_str());
+#endif
+ }
+ else if (key == "ban_pickup")
+ {
+ append_vector(Options.banned_objects, split_string(",", field));
+ }
+ else if (key == "pickup_thrown")
+ {
+ Options.pickup_thrown = read_bool(field, Options.pickup_thrown);
+ }
+ else if (key == "pickup_dropped")
+ {
+ Options.pickup_dropped = read_bool(field, Options.pickup_dropped);
+ }
+ else if (key == "show_waypoints")
+ {
+ Options.show_waypoints = read_bool(field, Options.show_waypoints);
+ }
+ else if (key == "travel_delay")
+ {
+ // Read travel delay in milliseconds.
+ Options.travel_delay = atoi( field.c_str() );
+ if (Options.travel_delay < -1)
+ Options.travel_delay = -1;
+ if (Options.travel_delay > 2000)
+ Options.travel_delay = 2000;
+ }
+ else if (key == "level_map_cursor_step")
+ {
+ Options.level_map_cursor_step = atoi( field.c_str() );
+ if (Options.level_map_cursor_step < 1)
+ Options.level_map_cursor_step = 1;
+ if (Options.level_map_cursor_step > 50)
+ Options.level_map_cursor_step = 50;
+ }
+ else if (key == "macro_meta_entry")
+ {
+ Options.macro_meta_entry = read_bool(field, Options.macro_meta_entry);
+ }
+ else if (key == "travel_stair_cost")
+ {
+ Options.travel_stair_cost = atoi( field.c_str() );
+ if (Options.travel_stair_cost < 1)
+ Options.travel_stair_cost = 1;
+ else if (Options.travel_stair_cost > 1000)
+ Options.travel_stair_cost = 1000;
+ }
+ else if (key == "travel_exclude_radius2")
+ {
+ Options.travel_exclude_radius2 = atoi( field.c_str() );
+ if (Options.travel_exclude_radius2 < 0)
+ Options.travel_exclude_radius2 = 0;
+ else if (Options.travel_exclude_radius2 > 400)
+ Options.travel_exclude_radius2 = 400;
+ }
+ else if (key == "stop_travel")
+ {
+ std::vector<std::string> fragments = split_string(",", field);
+ for (int i = 0, count = fragments.size(); i < count; ++i) {
+ if (fragments[i].length() == 0)
+ continue;
+
+ std::string::size_type pos = fragments[i].find(":");
+ if (pos && pos != std::string::npos) {
+ std::string prefix = fragments[i].substr(0, pos);
+ int channel = str_to_channel( prefix );
+ if (channel != -1 || prefix == "any") {
+ std::string s = fragments[i].substr( pos + 1 );
+ trim_string( s );
+ Options.stop_travel.push_back(
+ message_filter( channel, s ) );
+ continue;
+ }
}
+
+ Options.stop_travel.push_back(
+ message_filter( fragments[i] ) );
}
- else if (key == "fire_order")
- {
- str_to_fire_order( field, Options.fire_order );
- }
- else if (key == "random_pick")
- {
- // randomly generate character
- Options.random_pick = read_bool( field, Options.random_pick );
- }
- else if (key == "hp_warning")
+ }
+ else if (key == "drop_filter")
+ {
+ append_vector(Options.drop_filter, split_string(",", field));
+ }
+ else if (key == "travel_avoid_terrain")
+ {
+ std::vector<std::string> seg = split_string(",", field);
+ for (int i = 0, count = seg.size(); i < count; ++i)
+ prevent_travel_to( seg[i] );
+ }
+ else if (key == "travel_colour")
+ {
+ Options.travel_colour = read_bool(field, Options.travel_colour);
+ }
+ else if (key == "item_colour")
+ {
+ Options.item_colour = read_bool(field, Options.item_colour);
+ }
+ else if (key == "easy_exit_menu")
+ {
+ Options.easy_exit_menu = read_bool(field, Options.easy_exit_menu);
+ }
+ else if (key == "dos_use_background_intensity")
+ {
+ Options.dos_use_background_intensity =
+ read_bool(field, Options.dos_use_background_intensity);
+ }
+ else if (key == "explore_stop")
+ {
+ Options.explore_stop = ES_NONE;
+ std::vector<std::string> stops = split_string(",", field);
+ for (int i = 0, count = stops.size(); i < count; ++i)
{
- Options.hp_warning = atoi( field.c_str() );
- if (Options.hp_warning < 0 || Options.hp_warning > 100)
- {
- Options.hp_warning = 0;
- fprintf( stderr, "Bad HP warning percentage -- %s\n",
- field.c_str() );
- }
+ const std::string &c = stops[i];
+ if (c == "item" || c == "items")
+ Options.explore_stop |= ES_ITEM;
+ else if (c == "shop" || c == "shops")
+ Options.explore_stop |= ES_SHOP;
+ else if (c == "stair" || c == "stairs")
+ Options.explore_stop |= ES_STAIR;
+ else if (c == "altar" || c == "altars")
+ Options.explore_stop |= ES_ALTAR;
}
- else if (key == "hp_attention")
- {
- Options.hp_attention = atoi( field.c_str() );
- if (Options.hp_attention < 0 || Options.hp_attention > 100)
- {
- Options.hp_attention = 0;
- fprintf( stderr, "Bad HP attention percentage -- %s\n",
- field.c_str() );
+ }
+#ifdef STASH_TRACKING
+ else if (key == "stash_tracking")
+ {
+ Options.stash_tracking =
+ field == "explicit"? STM_EXPLICIT :
+ field == "dropped" ? STM_DROPPED :
+ field == "all" ? STM_ALL :
+ STM_NONE;
+ }
+ else if (key == "stash_filter")
+ {
+ std::vector<std::string> seg = split_string(",", field);
+ for (int i = 0, count = seg.size(); i < count; ++i)
+ Stash::filter( seg[i] );
+ }
+#endif
+ else if (key == "sound")
+ {
+ std::vector<std::string> seg = split_string(",", field);
+ for (int i = 0, count = seg.size(); i < count; ++i) {
+ const std::string &sub = seg[i];
+ std::string::size_type cpos = sub.find(":", 0);
+ if (cpos != std::string::npos) {
+ sound_mapping mapping;
+ mapping.pattern = sub.substr(0, cpos);
+ mapping.soundfile = sub.substr(cpos + 1);
+ Options.sound_mappings.push_back(mapping);
}
}
- else if (key == "crawl_dir")
- {
- // 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(), kNameLen - 1);
- SysEnv.crawl_dir[ kNameLen - 1 ] = '\0';
+ }
+ else if (key == "menu_colour" || key == "menu_color")
+ {
+ std::vector<std::string> seg = split_string(",", field);
+ for (int i = 0, count = seg.size(); i < count; ++i) {
+ const std::string &sub = seg[i];
+ std::string::size_type cpos = sub.find(":", 0);
+ if (cpos != std::string::npos) {
+ colour_mapping mapping;
+ mapping.pattern = sub.substr(cpos + 1);
+ mapping.colour = str_to_colour(sub.substr(0, cpos));
+ if (mapping.colour != -1)
+ Options.menu_colour_mappings.push_back(mapping);
}
}
- else if (key == "race")
- {
- Options.race = str_to_race( field );
-
- if (Options.race == '\0')
- fprintf( stderr, "Unknown race choice: %s\n", field.c_str() );
- }
- else if (key == "class")
- {
- Options.cls = str_to_class( field );
-
- if (Options.cls == '\0')
- fprintf( stderr, "Unknown class choice: %s\n", field.c_str() );
- }
- else if (key == "auto_list")
- {
- Options.auto_list = read_bool( field, Options.auto_list );
- }
- else if (key == "delay_message_clear")
- {
- Options.delay_message_clear = read_bool( field, Options.delay_message_clear );
- }
- else if (key == "terse_hand")
- {
- Options.terse_hand = read_bool( field, Options.terse_hand );
- }
- else if (key == "flush")
+ }
+ else if (key == "dump_kill_places")
+ {
+ Options.dump_kill_places =
+ field == "none"? KDO_NO_PLACES :
+ field == "all" ? KDO_ALL_PLACES :
+ KDO_ONE_PLACE;
+ }
+ else if (key == "kill_map")
+ {
+ std::vector<std::string> seg = split_string(",", field);
+ for (int i = 0, count = seg.size(); i < count; ++i)
{
- if (subkey == "failure")
- {
- Options.flush_input[FLUSH_ON_FAILURE]
- = read_bool(field, Options.flush_input[FLUSH_ON_FAILURE]);
- }
- else if (subkey == "command")
- {
- Options.flush_input[FLUSH_BEFORE_COMMAND]
- = read_bool(field, Options.flush_input[FLUSH_BEFORE_COMMAND]);
- }
- else if (subkey == "message")
- {
- Options.flush_input[FLUSH_ON_MESSAGE]
- = read_bool(field, Options.flush_input[FLUSH_ON_MESSAGE]);
+ const std::string &s = seg[i];
+ std::string::size_type cpos = s.find(":", 0);
+ if (cpos != std::string::npos) {
+ std::string from = s.substr(0, cpos);
+ std::string to = s.substr(cpos + 1);
+ do_kill_map(from, to);
}
}
- else if (key == "lowercase_invocations")
+ }
+ else if (key == "dump_message_count")
+ {
+ // Capping is implicit
+ Options.dump_message_count = atoi( field.c_str() );
+ }
+ else if (key == "dump_item_origins")
+ {
+ Options.dump_item_origins = IODS_PRICE;
+ std::vector<std::string> choices = split_string(",", field);
+ for (int i = 0, count = choices.size(); i < count; ++i)
{
- Options.lowercase_invocations
- = read_bool(field, Options.lowercase_invocations);
+ const std::string &ch = choices[i];
+ if (ch == "artifacts")
+ Options.dump_item_origins |= IODS_ARTIFACTS;
+ else if (ch == "ego_arm" || ch == "ego armour"
+ || ch == "ego_armour")
+ Options.dump_item_origins |= IODS_EGO_ARMOUR;
+ else if (ch == "ego_weap" || ch == "ego weapon"
+ || ch == "ego_weapon" || ch == "ego weapons"
+ || ch == "ego_weapons")
+ Options.dump_item_origins |= IODS_EGO_WEAPON;
+ else if (ch == "jewellery" || ch == "jewelry")
+ Options.dump_item_origins |= IODS_JEWELLERY;
+ else if (ch == "runes")
+ Options.dump_item_origins |= IODS_RUNES;
+ else if (ch == "rods")
+ Options.dump_item_origins |= IODS_RODS;
+ else if (ch == "staves")
+ Options.dump_item_origins |= IODS_STAVES;
+ else if (ch == "books")
+ Options.dump_item_origins |= IODS_BOOKS;
+ else if (ch == "all" || ch == "everything")
+ Options.dump_item_origins = IODS_EVERYTHING;
}
- else if (key == "wiz_mode")
+ }
+ else if (key == "dump_item_origin_price")
+ {
+ Options.dump_item_origin_price = atoi( field.c_str() );
+ if (Options.dump_item_origin_price < -1)
+ Options.dump_item_origin_price = -1;
+ }
+ else if (key == "target_zero_exp")
+ {
+ Options.target_zero_exp = read_bool(field, Options.target_zero_exp);
+ }
+ else if (key == "target_wrap")
+ {
+ Options.target_wrap = read_bool(field, Options.target_wrap);
+ }
+ else if (key == "target_oos")
+ {
+ Options.target_oos = read_bool(field, Options.target_oos);
+ }
+ else if (key == "target_los_first")
+ {
+ Options.target_los_first = read_bool(field, Options.target_los_first);
+ }
+ else if (key == "drop_mode")
+ {
+ if (field.find("multi") != std::string::npos)
+ Options.drop_mode = DM_MULTI;
+ else
+ Options.drop_mode = DM_SINGLE;
+ }
+ // Catch-all else, copies option into map
+ else
+ {
+#ifdef CLUA_BINDINGS
+ if (!clua.callbooleanfn(false, "c_process_lua_option", "ss",
+ key.c_str(), orig_field.c_str()))
+#endif
{
- // wiz_mode is recognized as a legal key in all compiles -- bwr
-#ifdef WIZARD
- if (field == "never")
- Options.wiz_mode = WIZ_NEVER;
- else if (field == "no")
- Options.wiz_mode = WIZ_NO;
- else if (field == "yes")
- Options.wiz_mode = WIZ_YES;
- else
- fprintf(stderr, "Unknown wiz_mode option: %s\n", field.c_str());
+#ifdef CLUA_BINDINGS
+ if (clua.error.length())
+ mpr(clua.error.c_str());
#endif
+ Options.named_options[key] = orig_field;
}
}
-
- fclose(f);
-} // end read_init_file()
+}
void get_system_environment(void)
{
diff --git a/trunk/source/initfile.h b/trunk/source/initfile.h
index 6b641eb523..d5860fdd77 100644
--- a/trunk/source/initfile.h
+++ b/trunk/source/initfile.h
@@ -13,6 +13,7 @@
#define INITFILE_H
#include <string>
+#include <cstdio>
std::string & trim_string( std::string &str );
@@ -20,8 +21,13 @@ std::string & trim_string( std::string &str );
/* ***********************************************************************
* called from: acr
* *********************************************************************** */
-void read_init_file(void);
+void read_init_file(bool runscript = false);
+void read_options(FILE *f, bool runscript = false);
+
+void read_options(const std::string &s, bool runscript = false);
+
+void parse_option_line(const std::string &line, bool runscript = false);
// last updated 12may2000 {dlb}
/* ***********************************************************************
@@ -36,5 +42,60 @@ void get_system_environment(void);
* *********************************************************************** */
bool parse_args(int argc, char **argv, bool rc_only);
+void write_newgame_options_file(void);
+
+void save_player_name(void);
+
+std::string channel_to_str(int ch);
+
+int str_to_channel(const std::string &);
+
+class InitLineInput {
+public:
+ virtual ~InitLineInput() { }
+ virtual bool eof() = 0;
+ virtual std::string getline() = 0;
+};
+
+class FileLineInput : public InitLineInput {
+public:
+ FileLineInput(FILE *f) : file(f) { }
+
+ bool eof() {
+ return !file || feof(file);
+ }
+
+ std::string getline() {
+ char s[256] = "";
+ if (!eof())
+ fgets(s, sizeof s, file);
+ return (s);
+ }
+private:
+ FILE *file;
+};
+
+class StringLineInput : public InitLineInput {
+public:
+ StringLineInput(const std::string &s) : str(s), pos(0) { }
+
+ bool eof() {
+ return pos >= str.length();
+ }
+
+ std::string getline() {
+ if (eof())
+ return "";
+ std::string::size_type newl = str.find("\n", pos);
+ if (newl == std::string::npos)
+ newl = str.length();
+ std::string line = str.substr(pos, newl - pos);
+ pos = newl + 1;
+ return line;
+ }
+private:
+ const std::string &str;
+ std::string::size_type pos;
+};
#endif
diff --git a/trunk/source/invent.cc b/trunk/source/invent.cc
index 1a6db7d1bc..2145f80f72 100644
--- a/trunk/source/invent.cc
+++ b/trunk/source/invent.cc
@@ -25,6 +25,7 @@
#include "externs.h"
+#include "clua.h"
#include "itemname.h"
#include "items.h"
#include "macro.h"
@@ -32,237 +33,467 @@
#include "shopping.h"
#include "stuff.h"
#include "view.h"
-
+#include "menu.h"
const char *command_string( int i );
const char *wizard_string( int i );
-unsigned char get_invent( int invent_type )
+struct InvTitle : public MenuEntry
{
- unsigned char nothing = invent( invent_type, false );
-
- redraw_screen();
+ Menu *m;
+ std::string (*titlefn)( int menuflags, const std::string &oldt );
+
+ InvTitle( Menu *mn, const char *title,
+ std::string (*tfn)( int menuflags, const std::string &oldt ) )
+ : MenuEntry( title )
+ {
+ m = mn;
+ titlefn = tfn;
+ }
- return (nothing);
-} // end get_invent()
+ std::string get_text() const
+ {
+ return titlefn? titlefn( m->get_flags(), MenuEntry::get_text() ) :
+ MenuEntry::get_text();
+ }
+};
-unsigned char invent( int item_class_inv, bool show_price )
+class InvShowPrices;
+class InvEntry : public MenuEntry
{
- char st_pass[ ITEMNAME_SIZE ] = "";
+private:
+ static bool show_prices;
+ static char temp_id[4][50];
+ static void set_show_prices(bool doshow);
- int i, j;
- char lines = 0;
- unsigned char anything = 0;
- char tmp_quant[20] = "";
- char yps = 0;
- char temp_id[4][50];
+ friend class InvShowPrices;
+public:
+ const item_def *item;
- const int num_lines = get_number_of_lines();
+ InvEntry( const item_def &i ) : MenuEntry( "", MEL_ITEM ), item( &i )
+ {
+ data = const_cast<item_def *>( item );
+
+ char buf[ITEMNAME_SIZE];
+ if (i.base_type == OBJ_GOLD)
+ snprintf(buf, sizeof buf, "%d gold piece%s", i.quantity,
+ (i.quantity > 1? "s" : ""));
+ else
+ item_name(i,
+ in_inventory(i)?
+ DESC_INVENTORY_EQUIP : DESC_NOCAP_A, buf, false);
+ text = buf;
- FixedVector< int, NUM_OBJECT_CLASSES > inv_class2;
- int inv_count = 0;
- unsigned char ki = 0;
+ if (i.base_type != OBJ_GOLD)
+ {
+ if (in_inventory(i))
+ {
+ text = text.substr( 4 ); // Skip the inventory letter.
+ add_hotkey(index_to_letter( i.link ));
+ }
+ else
+ add_hotkey(' '); // Dummy hotkey
+ }
+ else
+ {
+ // Dummy hotkey for gold.
+ add_hotkey(' ');
+ }
+ add_class_hotkeys(i);
-#ifdef DOS_TERM
- char buffer[4600];
+ quantity = i.quantity;
+ }
- gettext(1, 1, 80, 25, buffer);
- window(1, 1, 80, 25);
-#endif
+ std::string get_text() const
+ {
+ char buf[ITEMNAME_SIZE];
+ char suffix[ITEMNAME_SIZE] = "";
- for (i = 0; i < 4; i++)
+ if (InvEntry::show_prices)
+ {
+ int value = item_value(*item, temp_id, true);
+ if (value > 0)
+ snprintf(suffix, sizeof suffix,
+ " (%d gold)", value);
+ }
+ snprintf( buf, sizeof buf,
+ "%c %c %s%s",
+ hotkeys[0],
+ (!selected_qty? '-' : selected_qty < quantity? '#' : '+'),
+ text.c_str(),
+ suffix );
+ return (buf);
+ }
+private:
+ void add_class_hotkeys(const item_def &i)
{
- for (j = 0; j < 50; j++)
+ switch (i.base_type)
{
- temp_id[i][j] = 1;
+ case OBJ_GOLD:
+ add_hotkey('$');
+ break;
+ case OBJ_MISSILES:
+ add_hotkey('(');
+ break;
+ case OBJ_WEAPONS:
+ add_hotkey(')');
+ break;
+ case OBJ_ARMOUR:
+ add_hotkey('[');
+ break;
+ case OBJ_WANDS:
+ add_hotkey('/');
+ break;
+ case OBJ_FOOD:
+ add_hotkey('%');
+ break;
+ case OBJ_BOOKS:
+ add_hotkey('+');
+ add_hotkey(':');
+ break;
+ case OBJ_SCROLLS:
+ add_hotkey('?');
+ break;
+ case OBJ_JEWELLERY:
+ add_hotkey(i.sub_type >= AMU_RAGE? '"' : '=');
+ break;
+ case OBJ_POTIONS:
+ add_hotkey('!');
+ break;
+ case OBJ_STAVES:
+ add_hotkey('\\');
+ add_hotkey('|');
+ break;
+ case OBJ_MISCELLANY:
+ add_hotkey('}');
+ break;
+ case OBJ_CORPSES:
+ add_hotkey('&');
+ break;
+ default:
+ break;
}
}
+};
- clrscr();
-
- for (i = 0; i < NUM_OBJECT_CLASSES; i++)
- inv_class2[i] = 0;
+bool InvEntry::show_prices = false;
+char InvEntry::temp_id[4][50];
- for (i = 0; i < ENDOFPACK; i++)
+void InvEntry::set_show_prices(bool doshow)
+{
+ if ((show_prices = doshow))
{
- if (you.inv[i].quantity)
- {
- inv_class2[ you.inv[i].base_type ]++;
- inv_count++;
- }
+ memset(temp_id, 1, sizeof temp_id);
}
+}
- if (!inv_count)
+class InvShowPrices {
+public:
+ InvShowPrices(bool doshow = true)
{
- cprintf("You aren't carrying anything.");
+ InvEntry::set_show_prices(doshow);
+ }
+ ~InvShowPrices()
+ {
+ InvEntry::set_show_prices(false);
+ }
+};
- if (getch() == 0)
- getch();
+class InvMenu : public Menu
+{
+public:
+ InvMenu(int flags = MF_MULTISELECT) : Menu(flags) { }
+protected:
+ bool process_key(int key);
+};
- goto putty;
+bool InvMenu::process_key( int key )
+{
+ if (items.size() && (key == CONTROL('D') || key == '@'))
+ {
+ int newflag =
+ is_set(MF_MULTISELECT)?
+ MF_SINGLESELECT | MF_EASY_EXIT | MF_ANYPRINTABLE
+ : MF_MULTISELECT;
+
+ flags &= ~(MF_SINGLESELECT | MF_MULTISELECT |
+ MF_EASY_EXIT | MF_ANYPRINTABLE);
+ flags |= newflag;
+
+ deselect_all();
+ sel->clear();
+ draw_select_count(0);
+ return true;
}
+ return Menu::process_key( key );
+}
- if (item_class_inv != -1)
- {
- for (i = 0; i < NUM_OBJECT_CLASSES; i++)
- {
- if (item_class_inv == OBJ_MISSILES && i == OBJ_WEAPONS)
- i++;
+bool in_inventory( const item_def &i )
+{
+ return i.x == -1 && i.y == -1;
+}
- if (item_class_inv == OBJ_WEAPONS
- && (i == OBJ_STAVES || i == OBJ_MISCELLANY))
- {
- i++;
- }
+unsigned char get_invent( int invent_type )
+{
+ unsigned char nothing = invent_select( invent_type );
+
+ redraw_screen();
- if (item_class_inv == OBJ_SCROLLS && i == OBJ_BOOKS)
- i++;
+ return (nothing);
+} // end get_invent()
- if (i < NUM_OBJECT_CLASSES && item_class_inv != i)
- inv_class2[i] = 0;
+std::string item_class_name( int type, bool terse )
+{
+ if (terse)
+ {
+ switch (type)
+ {
+ case OBJ_GOLD: return ("gold");
+ case OBJ_WEAPONS: return ("weapon");
+ case OBJ_MISSILES: return ("missile");
+ case OBJ_ARMOUR: return ("armour");
+ case OBJ_WANDS: return ("wand");
+ case OBJ_FOOD: return ("food");
+ case OBJ_UNKNOWN_I: return ("book");
+ case OBJ_SCROLLS: return ("scroll");
+ case OBJ_JEWELLERY: return ("jewelry");
+ case OBJ_POTIONS: return ("potion");
+ case OBJ_UNKNOWN_II: return ("gem");
+ case OBJ_BOOKS: return ("book");
+ case OBJ_STAVES: return ("stave");
+ case OBJ_ORBS: return ("orb");
+ case OBJ_MISCELLANY: return ("misc");
+ case OBJ_CORPSES: return ("carrion");
+ }
+ }
+ else
+ {
+ switch (type)
+ {
+ case OBJ_GOLD: return ("Gold");
+ case OBJ_WEAPONS: return ("Hand Weapons");
+ case OBJ_MISSILES: return ("Missiles");
+ case OBJ_ARMOUR: return ("Armour");
+ case OBJ_WANDS: return ("Magical Devices");
+ case OBJ_FOOD: return ("Comestibles");
+ case OBJ_UNKNOWN_I: return ("Books");
+ case OBJ_SCROLLS: return ("Scrolls");
+ case OBJ_JEWELLERY: return ("Jewellery");
+ case OBJ_POTIONS: return ("Potions");
+ case OBJ_UNKNOWN_II: return ("Gems");
+ case OBJ_BOOKS: return ("Books");
+ case OBJ_STAVES: return ("Magical Staves and Rods");
+ case OBJ_ORBS: return ("Orbs of Power");
+ case OBJ_MISCELLANY: return ("Miscellaneous");
+ case OBJ_CORPSES: return ("Carrion");
}
}
+ return ("");
+}
- if ((item_class_inv == -1 && inv_count > 0)
- || (item_class_inv != -1 && inv_class2[item_class_inv] > 0)
- || (item_class_inv == OBJ_MISSILES && inv_class2[OBJ_WEAPONS] > 0)
- || (item_class_inv == OBJ_WEAPONS
- && (inv_class2[OBJ_STAVES] > 0 || inv_class2[OBJ_MISCELLANY] > 0))
- || (item_class_inv == OBJ_SCROLLS && inv_class2[OBJ_BOOKS] > 0))
+void populate_item_menu( Menu *menu, const std::vector<item_def> &items,
+ void (*callback)(MenuEntry *me) )
+{
+ FixedVector< int, NUM_OBJECT_CLASSES > inv_class;
+ for (int i = 0; i < NUM_OBJECT_CLASSES; ++i)
+ inv_class[i] = 0;
+ for (int i = 0, count = items.size(); i < count; ++i)
+ inv_class[ items[i].base_type ]++;
+
+ menu_letter ckey;
+ for (int i = 0; i < NUM_OBJECT_CLASSES; ++i)
{
- const int cap = carrying_capacity();
+ if (!inv_class[i]) continue;
- cprintf( " Inventory: %d.%d aum (%d%% of %d.%d aum maximum)",
- you.burden / 10, you.burden % 10,
- (you.burden * 100) / cap, cap / 10, cap % 10 );
- lines++;
+ menu->add_entry( new MenuEntry( item_class_name(i), MEL_SUBTITLE ) );
- for (i = 0; i < 15; i++)
+ for (int j = 0, count = items.size(); j < count; ++j)
{
- if (inv_class2[i] != 0)
- {
- if (lines > num_lines - 3)
- {
- gotoxy(1, num_lines);
- cprintf("-more-");
+ if (items[j].base_type != i) continue;
- ki = getch();
+ InvEntry *ie = new InvEntry( items[j] );
+ ie->hotkeys[0] = ckey++;
+ callback(ie);
- if (ki == ESCAPE)
- {
-#ifdef DOS_TERM
- puttext(1, 1, 80, 25, buffer);
-#endif
- return (ESCAPE);
- }
- else if (isalpha(ki) || ki == '?' || ki == '*')
- {
-#ifdef DOS_TERM
- puttext(1, 1, 80, 25, buffer);
-#endif
- return (ki);
- }
+ menu->add_entry( ie );
+ }
+ }
+}
- if (ki == 0)
- ki = getch();
+std::vector<SelItem> select_items( std::vector<item_def*> &items,
+ const char *title )
+{
+ std::vector<SelItem> selected;
- lines = 0;
- clrscr();
- gotoxy(1, 1);
- anything = 0;
+ if (items.empty())
+ return selected;
- }
+ FixedVector< int, NUM_OBJECT_CLASSES > inv_class;
+ for (int i = 0; i < NUM_OBJECT_CLASSES; ++i)
+ inv_class[i] = 0;
+ for (int i = 0, count = items.size(); i < count; ++i)
+ inv_class[ items[i]->base_type ]++;
- if (lines > 0)
- cprintf(EOL " ");
+ Menu menu;
+ menu.set_title( new MenuEntry(title) );
- textcolor(BLUE);
+ char ckey = 'a';
+ for (int i = 0; i < NUM_OBJECT_CLASSES; ++i)
+ {
+ if (!inv_class[i]) continue;
- switch (i)
- {
- case OBJ_WEAPONS: cprintf("Hand Weapons"); break;
- case OBJ_MISSILES: cprintf("Missiles"); break;
- case OBJ_ARMOUR: cprintf("Armour"); break;
- case OBJ_WANDS: cprintf("Magical Devices"); break;
- case OBJ_FOOD: cprintf("Comestibles"); break;
- case OBJ_UNKNOWN_I: cprintf("Books"); break;
- case OBJ_SCROLLS: cprintf("Scrolls"); break;
- case OBJ_JEWELLERY: cprintf("Jewellery"); break;
- case OBJ_POTIONS: cprintf("Potions"); break;
- case OBJ_UNKNOWN_II: cprintf("Gems"); break;
- case OBJ_BOOKS: cprintf("Books"); break;
- case OBJ_STAVES: cprintf("Magical Staves and Rods"); break;
- case OBJ_ORBS: cprintf("Orbs of Power"); break;
- case OBJ_MISCELLANY: cprintf("Miscellaneous"); break;
- case OBJ_CORPSES: cprintf("Carrion"); break;
- //case OBJ_GEMSTONES: cprintf("Miscellaneous"); break;
- }
-
- textcolor(LIGHTGREY);
- lines++;
-
- for (j = 0; j < ENDOFPACK; j++)
- {
- if (lines > num_lines - 2 && inv_count > 0)
- {
- gotoxy(1, num_lines);
- cprintf("-more-");
- ki = getch();
+ menu.add_entry( new MenuEntry( item_class_name(i), MEL_SUBTITLE ) );
- if (ki == ESCAPE)
- {
-#ifdef DOS_TERM
- puttext(1, 1, 80, 25, buffer);
-#endif
- return (ESCAPE);
- }
- else if (isalpha(ki) || ki == '?' || ki == '*')
- {
-#ifdef DOS_TERM
- puttext(1, 1, 80, 25, buffer);
+ for (int j = 0, count = items.size(); j < count; ++j)
+ {
+ if (items[j]->base_type != i) continue;
+
+ InvEntry *ie = new InvEntry( *items[j] );
+ ie->hotkeys[0] = ckey;
+
+ menu.add_entry( ie );
+
+ ckey = ckey == 'z'? 'A' :
+ ckey == 'Z'? 'a' :
+ ckey + 1;
+ }
+ }
+ menu.set_flags( MF_MULTISELECT | MF_SELECT_ANY_PAGE );
+ std::vector< MenuEntry * > sel = menu.show();
+ for (int i = 0, count = sel.size(); i < count; ++i)
+ {
+ InvEntry *inv = (InvEntry *) sel[i];
+ selected.push_back( SelItem( inv->hotkeys[0],
+ inv->selected_qty,
+ inv->item ) );
+ }
+
+ return selected;
+}
+
+static bool item_class_selected(const item_def &i, int selector)
+{
+ const int itype = i.base_type;
+ if (selector == OSEL_ANY || selector == itype)
+ return (true);
+
+ switch (selector)
+ {
+ case OBJ_MISSILES:
+ return (itype == OBJ_MISSILES || itype == OBJ_WEAPONS);
+ case OBJ_WEAPONS:
+ case OSEL_WIELD:
+ return (itype == OBJ_WEAPONS || itype == OBJ_STAVES
+ || itype == OBJ_MISCELLANY);
+ case OBJ_SCROLLS:
+ return (itype == OBJ_SCROLLS || itype == OBJ_BOOKS);
+ default:
+ return (false);
+ }
+}
+
+static bool userdef_item_selected(const item_def &i, int selector)
+{
+#if defined(CLUA_BINDINGS)
+ const char *luafn = selector == OSEL_WIELD? "ch_item_wieldable" :
+ NULL;
+ return (luafn && clua.callbooleanfn(false, luafn, "u", &i));
+#else
+ return (false);
#endif
- return (ki);
- }
+}
- if (ki == 0)
- ki = getch();
+static bool is_item_selected(const item_def &i, int selector)
+{
+ return item_class_selected(i, selector)
+ || userdef_item_selected(i, selector);
+}
- lines = 0;
- clrscr();
- gotoxy(1, 1);
- anything = 0;
- }
+static void get_inv_items_to_show(std::vector<item_def*> &v, int selector)
+{
+ for (int i = 0; i < ENDOFPACK; i++)
+ {
+ if (is_valid_item(you.inv[i]) && is_item_selected(you.inv[i], selector))
+ v.push_back( &you.inv[i] );
+ }
+}
- if (is_valid_item(you.inv[j]) && you.inv[j].base_type==i)
- {
- anything++;
+static void set_vectitem_classes(const std::vector<item_def*> &v,
+ FixedVector<int, NUM_OBJECT_CLASSES> &fv)
+{
+ for (int i = 0; i < NUM_OBJECT_CLASSES; i++)
+ fv[i] = 0;
+
+ for (int i = 0, size = v.size(); i < size; i++)
+ fv[ v[i]->base_type ]++;
+}
- if (lines > 0)
- cprintf(EOL);
+unsigned char invent_select(
+ int item_selector,
+ int flags,
+ std::string (*titlefn)( int menuflags,
+ const std::string &oldt ),
+ std::vector<SelItem> *items,
+ std::vector<text_pattern> *filter,
+ Menu::selitem_tfn selitemfn )
+{
+ InvMenu menu;
+
+ menu.selitem_text = selitemfn;
+ if (filter)
+ menu.set_select_filter( *filter );
- lines++;
+ FixedVector< int, NUM_OBJECT_CLASSES > inv_class2;
+ for (int i = 0; i < NUM_OBJECT_CLASSES; i++)
+ inv_class2[i] = 0;
- yps = wherey();
+ int inv_count = 0;
- in_name( j, DESC_INVENTORY_EQUIP, st_pass );
- cprintf( st_pass );
+ for (int i = 0; i < ENDOFPACK; i++)
+ {
+ if (you.inv[i].quantity)
+ {
+ inv_class2[ you.inv[i].base_type ]++;
+ inv_count++;
+ }
+ }
- inv_count--;
+ if (!inv_count)
+ {
+ menu.set_title( new MenuEntry( "You aren't carrying anything." ) );
+ menu.show();
+ return 0;
+ }
+ std::vector<item_def*> tobeshown;
+ get_inv_items_to_show( tobeshown, item_selector );
+ set_vectitem_classes( tobeshown, inv_class2 );
- if (show_price)
- {
- cprintf(" (");
+ if (tobeshown.size())
+ {
+ const int cap = carrying_capacity();
- itoa( item_value( you.inv[j], temp_id, true ),
- tmp_quant, 10 );
+ char title_buf[200];
+ snprintf( title_buf, sizeof title_buf,
+ "Inventory: %d.%d/%d.%d aum (%d%%, %d/52 slots)",
+ you.burden / 10, you.burden % 10,
+ cap / 10, cap % 10,
+ (you.burden * 100) / cap,
+ inv_count );
+ menu.set_title( new InvTitle( &menu, title_buf, titlefn ) );
- cprintf( tmp_quant );
- cprintf( " gold)" );
- }
+ for (int i = 0; i < 15; i++)
+ {
+ if (inv_class2[i] != 0)
+ {
+ std::string s = item_class_name(i);
+ menu.add_entry( new MenuEntry( s, MEL_SUBTITLE ) );
- if (wherey() != yps)
- lines++;
+ for (int j = 0, size = tobeshown.size(); j < size; ++j)
+ {
+ if (tobeshown[j]->base_type == i)
+ {
+ menu.add_entry( new InvEntry( *tobeshown[j] ) );
}
} // end of j loop
} // end of if inv_class2
@@ -270,43 +501,41 @@ unsigned char invent( int item_class_inv, bool show_price )
}
else
{
- if (item_class_inv == -1)
- cprintf("You aren't carrying anything.");
+ std::string s;
+ if (item_selector == -1)
+ s = "You aren't carrying anything.";
else
{
- if (item_class_inv == OBJ_WEAPONS)
- cprintf("You aren't carrying any weapons.");
- else if (item_class_inv == OBJ_MISSILES)
- cprintf("You aren't carrying any ammunition.");
+ if (item_selector == OBJ_WEAPONS || item_selector == OSEL_WIELD)
+ s = "You aren't carrying any weapons.";
+ else if (item_selector == OBJ_MISSILES)
+ s = "You aren't carrying any ammunition.";
else
- cprintf("You aren't carrying any such object.");
-
- anything++;
+ s = "You aren't carrying any such object.";
}
+ menu.set_title( new MenuEntry( s ) );
}
- if (anything > 0)
+ menu.set_flags( flags );
+ std::vector< MenuEntry * > sel = menu.show();
+ if (items)
{
- ki = getch();
-
- if (isalpha(ki) || ki == '?' || ki == '*')
- {
-#ifdef DOS_TERM
- puttext(1, 1, 80, 25, buffer);
-#endif
- return (ki);
- }
-
- if (ki == 0)
- ki = getch();
+ for (int i = 0, count = sel.size(); i < count; ++i)
+ items->push_back( SelItem( sel[i]->hotkeys[0],
+ sel[i]->selected_qty ) );
}
- putty:
-#ifdef DOS_TERM
- puttext(1, 1, 80, 25, buffer);
-#endif
+ unsigned char mkey = menu.getkey();
+ if (!isalnum(mkey) && mkey != '$' && mkey != '-' && mkey != '?'
+ && mkey != '*')
+ mkey = ' ';
+ return mkey;
+}
- return (ki);
+unsigned char invent( int item_class_inv, bool show_price )
+{
+ InvShowPrices show_item_prices(show_price);
+ return (invent_select(item_class_inv));
} // end invent()
@@ -337,6 +566,140 @@ static unsigned char get_invent_quant( unsigned char keyin, int &quant )
return (keyin);
}
+// This function prompts the user for an item, handles the '?' and '*'
+// listings, and returns the inventory slot to the caller (which if
+// must_exist is true (the default) will be an assigned item, with
+// a positive quantity.
+//
+// It returns PROMPT_ABORT if the player hits escape.
+// It returns PROMPT_GOT_SPECIAL if the player hits the "other_valid_char".
+//
+// Note: This function never checks if the item is appropriate.
+std::vector<SelItem> prompt_invent_items(
+ const char *prompt,
+ int type_expect,
+ std::string (*titlefn)( int menuflags,
+ const std::string &oldt ),
+ bool allow_auto_list,
+ bool allow_easy_quit,
+ const char other_valid_char,
+ std::vector<text_pattern> *select_filter,
+ Menu::selitem_tfn fn )
+{
+ unsigned char keyin = 0;
+ int ret = -1;
+
+ bool need_redraw = false;
+ bool need_prompt = true;
+ bool need_getch = true;
+
+ if (Options.auto_list && allow_auto_list)
+ {
+ need_getch = false;
+ need_redraw = false;
+ need_prompt = false;
+ keyin = '?';
+ }
+
+ std::vector< SelItem > items;
+ int count = -1;
+ for (;;)
+ {
+ if (need_redraw)
+ {
+ redraw_screen();
+ mesclr( true );
+ }
+
+ if (need_prompt)
+ mpr( prompt, MSGCH_PROMPT );
+
+ if (need_getch)
+ keyin = get_ch();
+
+ need_redraw = false;
+ need_prompt = true;
+ need_getch = true;
+
+ // Note: We handle any "special" character first, so that
+ // it can be used to override the others.
+ if (other_valid_char != '\0' && keyin == other_valid_char)
+ {
+ ret = PROMPT_GOT_SPECIAL;
+ break;
+ }
+ else if (keyin == '?' || keyin == '*' || keyin == ',')
+ {
+ int selmode = Options.drop_mode == DM_SINGLE?
+ MF_SINGLESELECT | MF_EASY_EXIT | MF_ANYPRINTABLE :
+ MF_MULTISELECT;
+ // The "view inventory listing" mode.
+ int ch = invent_select(
+ keyin == '*'? -1 : type_expect,
+ selmode | MF_SELECT_ANY_PAGE,
+ titlefn, &items, select_filter, fn );
+
+ if (selmode & MF_SINGLESELECT)
+ {
+ keyin = ch;
+ need_getch = false;
+ }
+ else
+ {
+ keyin = 0;
+ need_getch = true;
+ }
+
+ if (items.size())
+ {
+ redraw_screen();
+ mesclr( true );
+
+ for (int i = 0, count = items.size(); i < count; ++i)
+ items[i].slot = letter_to_index( items[i].slot );
+ return items;
+ }
+
+ need_redraw = !(keyin == '?' || keyin == '*'
+ || keyin == ',' || keyin == '+');
+ need_prompt = need_redraw;
+ }
+ else if (isdigit( keyin ))
+ {
+ // The "read in quantity" mode
+ keyin = get_invent_quant( keyin, count );
+
+ need_prompt = false;
+ need_getch = false;
+ }
+ else if (keyin == ESCAPE
+ || (Options.easy_quit_item_prompts
+ && allow_easy_quit
+ && keyin == ' '))
+ {
+ ret = PROMPT_ABORT;
+ break;
+ }
+ else if (isalpha( keyin ))
+ {
+ ret = letter_to_index( keyin );
+
+ if (!is_valid_item( you.inv[ret] ))
+ mpr( "You do not have any such object." );
+ else
+ break;
+ }
+ else if (!isspace( keyin ))
+ {
+ // we've got a character we don't understand...
+ canned_msg( MSG_HUH );
+ }
+ }
+
+ if (ret != PROMPT_ABORT)
+ items.push_back( SelItem( ret, count ) );
+ return items;
+}
// This function prompts the user for an item, handles the '?' and '*'
// listings, and returns the inventory slot to the caller (which if
@@ -362,8 +725,22 @@ int prompt_invent_item( const char *prompt, int type_expect,
if (Options.auto_list && allow_auto_list)
{
+ std::vector< SelItem > items;
+
// pretend the player has hit '?' and setup state.
- keyin = invent( type_expect, false );
+ keyin = invent_select( type_expect,
+ MF_SINGLESELECT | MF_ANYPRINTABLE
+ | MF_EASY_EXIT,
+ NULL, &items );
+
+ if (items.size())
+ {
+ if (count)
+ *count = items[0].quantity;
+ redraw_screen();
+ mesclr( true );
+ return letter_to_index( keyin );
+ }
need_getch = false;
@@ -402,10 +779,21 @@ int prompt_invent_item( const char *prompt, int type_expect,
else if (keyin == '?' || keyin == '*')
{
// The "view inventory listing" mode.
- if (keyin == '*')
- keyin = invent( -1, false );
- else
- keyin = invent( type_expect, false );
+ std::vector< SelItem > items;
+ keyin = invent_select( keyin == '*'? OSEL_ANY : type_expect,
+ MF_SINGLESELECT | MF_ANYPRINTABLE
+ | MF_EASY_EXIT,
+ NULL,
+ &items );
+
+ if (items.size())
+ {
+ if (count)
+ *count = items[0].quantity;
+ redraw_screen();
+ mesclr( true );
+ return letter_to_index( keyin );
+ }
need_getch = false;
@@ -619,7 +1007,15 @@ const char *command_string( int i )
(i == 450) ? "Ctrl-X : Save game without query" :
#ifdef ALLOW_DESTROY_ITEM_COMMAND
- (i == 455) ? "Ctrl-D : Destroy inventory item" :
+ (i == 451) ? "Ctrl-D : Destroy inventory item" :
+#endif
+ (i == 453) ? "Ctrl-G : interlevel travel" :
+ (i == 455) ? "Ctrl-O : explore" :
+
+#ifdef STASH_TRACKING
+ (i == 456) ? "Ctrl-S : mark stash" :
+ (i == 457) ? "Ctrl-E : forget stash" :
+ (i == 458) ? "Ctrl-F : search stashes" :
#endif
(i == 460) ? "Shift & DIR : long walk" :
diff --git a/trunk/source/invent.h b/trunk/source/invent.h
index 2de7636200..624e603fc1 100644
--- a/trunk/source/invent.h
+++ b/trunk/source/invent.h
@@ -13,10 +13,26 @@
#define INVENT_H
#include <stddef.h>
+#include <vector>
+#include "menu.h"
#define PROMPT_ABORT -1
#define PROMPT_GOT_SPECIAL -2
+struct SelItem
+{
+ int slot;
+ int quantity;
+ const item_def *item;
+
+ SelItem() : slot(0), quantity(0), item(NULL) { }
+ SelItem( int s, int q, const item_def *it = NULL )
+ : slot(s), quantity(q), item(it)
+ {
+ }
+};
+
+
int prompt_invent_item( const char *prompt, int type_expect,
bool must_exist = true,
bool allow_auto_list = true,
@@ -24,12 +40,36 @@ int prompt_invent_item( const char *prompt, int type_expect,
const char other_valid_char = '\0',
int *const count = NULL );
+std::vector<SelItem> select_items( std::vector<item_def*> &items,
+ const char *title );
+
+std::vector<SelItem> prompt_invent_items(
+ const char *prompt,
+ int type_expect,
+ std::string (*titlefn)( int menuflags,
+ const std::string &oldt )
+ = NULL,
+ bool allow_auto_list = true,
+ bool allow_easy_quit = true,
+ const char other_valid_char = '\0',
+ std::vector<text_pattern> *filter = NULL,
+ Menu::selitem_tfn fn = NULL );
+
+
// last updated 12may2000 {dlb}
/* ***********************************************************************
* called from: invent - ouch - shopping
* *********************************************************************** */
-unsigned char invent(int item_class_inv, bool show_price);
+unsigned char invent( int item_class_inv, bool show_price );
+unsigned char invent_select(int item_class_inv,
+ int select_flags = MF_NOSELECT,
+ std::string (*titlefn)( int menuflags,
+ const std::string &oldt )
+ = NULL,
+ std::vector<SelItem> *sels = NULL,
+ std::vector<text_pattern> *filter = NULL,
+ Menu::selitem_tfn fn = NULL );
// last updated 24may2000 {dlb}
/* ***********************************************************************
@@ -37,6 +77,7 @@ unsigned char invent(int item_class_inv, bool show_price);
* *********************************************************************** */
unsigned char get_invent(int invent_type);
+bool in_inventory(const item_def &i);
// last updated 12may2000 {dlb}
/* ***********************************************************************
@@ -44,5 +85,9 @@ unsigned char get_invent(int invent_type);
* *********************************************************************** */
void list_commands(bool wizard);
+std::string item_class_name(int type, bool terse = false);
+
+void populate_item_menu( Menu *menu, const std::vector<item_def> &items,
+ void (*callback)(MenuEntry *me) );
#endif
diff --git a/trunk/source/item_use.cc b/trunk/source/item_use.cc
index b36a097eed..7ea1db4900 100644
--- a/trunk/source/item_use.cc
+++ b/trunk/source/item_use.cc
@@ -72,7 +72,61 @@ void use_randart(unsigned char item_wield_2);
static bool enchant_weapon( int which_stat, bool quiet = false );
static bool enchant_armour( void );
-void wield_weapon(bool auto_wield)
+// Rather messy - we've gathered all the can't-wield logic from wield_weapon()
+// here.
+bool can_wield(const item_def& weapon)
+{
+ if (you.berserker) return false;
+ if (you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE
+ && !can_equip( EQ_WEAPON ))
+ return false;
+
+ if (you.equip[EQ_WEAPON] != -1
+ && you.inv[you.equip[EQ_WEAPON]].base_type == OBJ_WEAPONS
+ && item_cursed( you.inv[you.equip[EQ_WEAPON]] ))
+ return false;
+
+ if (weapon.base_type != OBJ_WEAPONS && weapon.base_type == OBJ_STAVES
+ && you.equip[EQ_SHIELD] != -1)
+ return false;
+
+ if ((you.species < SP_OGRE || you.species > SP_OGRE_MAGE)
+ && mass_item( weapon ) >= 500)
+ return false;
+
+ if ((you.species == SP_HALFLING || you.species == SP_GNOME
+ || you.species == SP_KOBOLD || you.species == SP_SPRIGGAN)
+ && (weapon.sub_type == WPN_GREAT_SWORD
+ || weapon.sub_type == WPN_TRIPLE_SWORD
+ || weapon.sub_type == WPN_GREAT_MACE
+ || weapon.sub_type == WPN_GREAT_FLAIL
+ || weapon.sub_type == WPN_BATTLEAXE
+ || weapon.sub_type == WPN_EXECUTIONERS_AXE
+ || weapon.sub_type == WPN_HALBERD
+ || weapon.sub_type == WPN_GLAIVE
+ || weapon.sub_type == WPN_GIANT_CLUB
+ || weapon.sub_type == WPN_GIANT_SPIKED_CLUB
+ || weapon.sub_type == WPN_SCYTHE))
+ return false;
+
+ if (hands_reqd_for_weapon( weapon.base_type,
+ weapon.sub_type ) == HANDS_TWO_HANDED
+ && you.equip[EQ_SHIELD] != -1)
+ return false;
+
+ int weap_brand = get_weapon_brand( weapon );
+
+ if ((you.is_undead || you.species == SP_DEMONSPAWN)
+ && (!is_fixed_artefact( weapon )
+ && (weap_brand == SPWPN_HOLY_WRATH
+ || weap_brand == SPWPN_DISRUPTION)))
+ return false;
+
+ // We can wield this weapon. Phew!
+ return true;
+}
+
+bool wield_weapon(bool auto_wield, int slot, bool show_weff_messages)
{
int item_slot = 0;
char str_pass[ ITEMNAME_SIZE ];
@@ -80,13 +134,13 @@ void wield_weapon(bool auto_wield)
if (inv_count() < 1)
{
canned_msg(MSG_NOTHING_CARRIED);
- return;
+ return (false);
}
if (you.berserker)
{
canned_msg(MSG_TOO_BERSERK);
- return;
+ return (false);
}
if (you.attribute[ATTR_TRANSFORMATION] != TRAN_NONE)
@@ -94,7 +148,7 @@ void wield_weapon(bool auto_wield)
if (!can_equip( EQ_WEAPON ))
{
mpr("You can't wield anything in your present form.");
- return;
+ return (false);
}
}
@@ -103,7 +157,7 @@ void wield_weapon(bool auto_wield)
&& item_cursed( you.inv[you.equip[EQ_WEAPON]] ))
{
mpr("You can't unwield your weapon to draw a new one!");
- return;
+ return (false);
}
if (you.sure_blade)
@@ -118,19 +172,28 @@ void wield_weapon(bool auto_wield)
item_slot = 1;
else
item_slot = 0;
+ if (slot != -1) item_slot = slot;
}
+ bool force_unwield =
+ you.inv[item_slot].base_type != OBJ_WEAPONS
+ && you.inv[item_slot].base_type != OBJ_MISSILES
+ && you.inv[item_slot].base_type != OBJ_STAVES;
+
// Prompt if not using the auto swap command,
// or if the swap slot is empty.
- if (!auto_wield || !is_valid_item( you.inv[item_slot] ))
+ if (!auto_wield || !is_valid_item(you.inv[item_slot]) || force_unwield)
{
- item_slot = prompt_invent_item( "Wield which item (- for none)?",
- OBJ_WEAPONS, true, true, true, '-' );
+ if (!auto_wield)
+ item_slot = prompt_invent_item( "Wield which item (- for none)?",
+ OSEL_WIELD, true, true, true, '-' );
+ else
+ item_slot = PROMPT_GOT_SPECIAL;
if (item_slot == PROMPT_ABORT)
{
canned_msg( MSG_OK );
- return;
+ return (false);
}
else if (item_slot == PROMPT_GOT_SPECIAL) // '-' or bare hands
{
@@ -148,14 +211,14 @@ void wield_weapon(bool auto_wield)
{
mpr( "You are already empty-handed." );
}
- return;
+ return (true);
}
}
if (item_slot == you.equip[EQ_WEAPON])
{
mpr("You are already wielding that!");
- return;
+ return (true);
}
for (int i = EQ_CLOAK; i <= EQ_AMULET; i++)
@@ -163,7 +226,7 @@ void wield_weapon(bool auto_wield)
if (item_slot == you.equip[i])
{
mpr("You are wearing that object!");
- return;
+ return (false);
}
}
@@ -173,7 +236,7 @@ void wield_weapon(bool auto_wield)
&& you.equip[EQ_SHIELD] != -1)
{
mpr("You can't wield that with a shield.");
- return;
+ return (false);
}
if (you.equip[EQ_WEAPON] != -1)
@@ -187,7 +250,7 @@ void wield_weapon(bool auto_wield)
&& mass_item( you.inv[item_slot] ) >= 500)
{
mpr("That's too large and heavy for you to wield.");
- return;
+ return (false);
}
if ((you.species == SP_HALFLING || you.species == SP_GNOME
@@ -206,7 +269,7 @@ void wield_weapon(bool auto_wield)
|| you.inv[item_slot].sub_type == WPN_SCYTHE))
{
mpr("That's too large for you to wield.");
- return;
+ return (false);
}
@@ -215,7 +278,7 @@ void wield_weapon(bool auto_wield)
&& you.equip[EQ_SHIELD] != -1)
{
mpr("You can't wield that with a shield.");
- return;
+ return (false);
}
int weap_brand = get_weapon_brand( you.inv[item_slot] );
@@ -227,7 +290,7 @@ void wield_weapon(bool auto_wield)
{
mpr("This weapon will not allow you to wield it.");
you.turn_is_over = 1;
- return;
+ return (false);
}
if (you.equip[EQ_WEAPON] != -1)
@@ -237,7 +300,7 @@ void wield_weapon(bool auto_wield)
}
// any oddness on wielding taken care of here
- wield_effects(item_slot, true);
+ wield_effects(item_slot, show_weff_messages);
in_name( item_slot, DESC_INVENTORY_EQUIP, str_pass );
mpr( str_pass );
@@ -251,6 +314,8 @@ void wield_weapon(bool auto_wield)
you.wield_change = true;
you.turn_is_over = 1;
+
+ return (true);
}
// provide a function for handling initial wielding of 'special'
@@ -550,6 +615,33 @@ void wear_armour(void)
do_wear_armour( armour_wear_2, false );
}
+int armour_equip_slot(const item_def &item)
+{
+ int wh_equip = EQ_BODY_ARMOUR;
+
+ switch (item.sub_type)
+ {
+ case ARM_BUCKLER:
+ case ARM_LARGE_SHIELD:
+ case ARM_SHIELD:
+ wh_equip = EQ_SHIELD;
+ break;
+ case ARM_CLOAK:
+ wh_equip = EQ_CLOAK;
+ break;
+ case ARM_HELMET:
+ wh_equip = EQ_HELMET;
+ break;
+ case ARM_GLOVES:
+ wh_equip = EQ_GLOVES;
+ break;
+ case ARM_BOOTS:
+ wh_equip = EQ_BOOTS;
+ break;
+ }
+ return (wh_equip);
+}
+
bool do_wear_armour( int item, bool quiet )
{
char wh_equip = 0;
@@ -632,28 +724,7 @@ bool do_wear_armour( int item, bool quiet )
}
}
- wh_equip = EQ_BODY_ARMOUR;
-
- switch (you.inv[item].sub_type)
- {
- case ARM_BUCKLER:
- case ARM_LARGE_SHIELD:
- case ARM_SHIELD:
- wh_equip = EQ_SHIELD;
- break;
- case ARM_CLOAK:
- wh_equip = EQ_CLOAK;
- break;
- case ARM_HELMET:
- wh_equip = EQ_HELMET;
- break;
- case ARM_GLOVES:
- wh_equip = EQ_GLOVES;
- break;
- case ARM_BOOTS:
- wh_equip = EQ_BOOTS;
- break;
- }
+ wh_equip = armour_equip_slot(you.inv[item]);
if (you.species == SP_NAGA && you.inv[item].sub_type == ARM_BOOTS
&& you.inv[item].plus2 == TBOOT_NAGA_BARDING
@@ -1129,6 +1200,8 @@ static void throw_it(struct bolt &pbolt, int throw_2)
// Making a copy of the item: changed only for venom launchers
item_def item = you.inv[throw_2];
item.quantity = 1;
+ item.slot = index_to_letter(item.link);
+ origin_set_unknown(item);
char str_pass[ ITEMNAME_SIZE ];
@@ -1663,6 +1736,11 @@ static void throw_it(struct bolt &pbolt, int throw_2)
pbolt.isBeam = false;
pbolt.isTracer = false;
+ // mark this item as thrown if it's a missile, so that we'll pick it up
+ // when we walk over it.
+ if (wepClass == OBJ_MISSILES || wepClass == OBJ_WEAPONS)
+ item.flags |= ISFLAG_THROWN;
+
// using copy, since the launched item might be differect (venom blowgun)
fire_beam( pbolt, &item );
@@ -1678,45 +1756,20 @@ static void throw_it(struct bolt &pbolt, int throw_2)
you.turn_is_over = 1;
} // end throw_it()
-void puton_ring(void)
+bool puton_item(int item_slot, bool prompt_finger)
{
- bool is_amulet = false;
- int item_slot;
- char str_pass[ ITEMNAME_SIZE ];
-
- if (inv_count() < 1)
- {
- canned_msg(MSG_NOTHING_CARRIED);
- return;
- }
-
- if (you.berserker)
- {
- canned_msg(MSG_TOO_BERSERK);
- return;
- }
-
- item_slot = prompt_invent_item( "Put on which piece of jewellery?",
- OBJ_JEWELLERY );
-
- if (item_slot == PROMPT_ABORT)
- {
- canned_msg( MSG_OK );
- return;
- }
-
if (item_slot == you.equip[EQ_LEFT_RING]
|| item_slot == you.equip[EQ_RIGHT_RING]
|| item_slot == you.equip[EQ_AMULET])
{
mpr("You've already put that on!");
- return;
+ return (true);
}
if (item_slot == you.equip[EQ_WEAPON])
{
mpr("You are wielding that object.");
- return;
+ return (false);
}
if (you.inv[item_slot].base_type != OBJ_JEWELLERY)
@@ -1724,10 +1777,10 @@ void puton_ring(void)
//jmf: let's not take our inferiority complex out on players, eh? :-p
//mpr("You're sadly mistaken if you consider that jewellery.")
mpr("You can only put on jewellery.");
- return;
+ return (false);
}
- is_amulet = (you.inv[item_slot].sub_type >= AMU_RAGE);
+ bool is_amulet = (you.inv[item_slot].sub_type >= AMU_RAGE);
if (!is_amulet) // ie it's a ring
{
@@ -1735,7 +1788,7 @@ void puton_ring(void)
&& item_cursed( you.inv[you.equip[EQ_GLOVES]] ))
{
mpr("You can't take your gloves off to put on a ring!");
- return;
+ return (false);
}
if (you.inv[item_slot].base_type == OBJ_JEWELLERY
@@ -1744,7 +1797,7 @@ void puton_ring(void)
{
// and you are trying to wear body you.equip.
mpr("You've already put a ring on each hand.");
- return;
+ return (false);
}
}
else if (you.equip[EQ_AMULET] != -1)
@@ -1757,7 +1810,7 @@ void puton_ring(void)
}
mpr(info);
- return;
+ return (false);
}
int hand_used = 0;
@@ -1772,20 +1825,28 @@ void puton_ring(void)
hand_used = 2;
else if (you.equip[EQ_LEFT_RING] == -1 && you.equip[EQ_RIGHT_RING] == -1)
{
- mpr("Put on which hand (l or r)?", MSGCH_PROMPT);
+ if (prompt_finger)
+ {
+ mpr("Put on which hand (l or r)?", MSGCH_PROMPT);
- int keyin = get_ch();
+ int keyin = get_ch();
- if (keyin == 'l')
- hand_used = 0;
- else if (keyin == 'r')
- hand_used = 1;
- else if (keyin == ESCAPE)
- return;
+ if (keyin == 'l')
+ hand_used = 0;
+ else if (keyin == 'r')
+ hand_used = 1;
+ else if (keyin == ESCAPE)
+ return (false);
+ else
+ {
+ mpr("You don't have such a hand!");
+ return (false);
+ }
+ }
else
{
- mpr("You don't have such a hand!");
- return;
+ // First ring goes on left hand if we're choosing automatically.
+ hand_used = 0;
}
}
@@ -1897,11 +1958,45 @@ void puton_ring(void)
// cursed or not, we know that since we've put the ring on
set_ident_flags( you.inv[item_slot], ISFLAG_KNOW_CURSE );
+ char str_pass[ ITEMNAME_SIZE ];
in_name( item_slot, DESC_INVENTORY_EQUIP, str_pass );
mpr( str_pass );
+
+ return (true);
+}
+
+bool puton_ring(int slot, bool prompt_finger)
+{
+ int item_slot;
+
+ if (inv_count() < 1)
+ {
+ canned_msg(MSG_NOTHING_CARRIED);
+ return (false);
+ }
+
+ if (you.berserker)
+ {
+ canned_msg(MSG_TOO_BERSERK);
+ return (false);
+ }
+
+ if (slot != -1)
+ item_slot = slot;
+ else
+ item_slot = prompt_invent_item( "Put on which piece of jewellery?",
+ OBJ_JEWELLERY );
+
+ if (item_slot == PROMPT_ABORT)
+ {
+ canned_msg( MSG_OK );
+ return (false);
+ }
+
+ return puton_item(item_slot, prompt_finger);
} // end puton_ring()
-void remove_ring(void)
+bool remove_ring(int slot)
{
int hand_used = 10;
int ring_wear_2;
@@ -1911,13 +2006,13 @@ void remove_ring(void)
&& you.equip[EQ_AMULET] == -1)
{
mpr("You aren't wearing any rings or amulets.");
- return;
+ return (false);
}
if (you.berserker)
{
canned_msg(MSG_TOO_BERSERK);
- return;
+ return (false);
}
if (you.equip[EQ_GLOVES] != -1
@@ -1925,7 +2020,7 @@ void remove_ring(void)
&& you.equip[EQ_AMULET] == -1)
{
mpr("You can't take your gloves off to remove any rings!");
- return;
+ return (false);
}
if (you.equip[EQ_LEFT_RING] != -1 && you.equip[EQ_RIGHT_RING] == -1
@@ -1948,19 +2043,21 @@ void remove_ring(void)
if (hand_used == 10)
{
- int equipn = prompt_invent_item( "Remove which piece of jewellery?",
- OBJ_JEWELLERY );
+ int equipn =
+ slot == -1? prompt_invent_item( "Remove which piece of jewellery?",
+ OBJ_JEWELLERY )
+ : slot;
if (equipn == PROMPT_ABORT)
{
canned_msg( MSG_OK );
- return;
+ return (false);
}
if (you.inv[equipn].base_type != OBJ_JEWELLERY)
{
mpr("That isn't a piece of jewellery.");
- return;
+ return (false);
}
if (you.equip[EQ_LEFT_RING] == equipn)
@@ -1972,7 +2069,7 @@ void remove_ring(void)
else
{
mpr("You aren't wearing that.");
- return;
+ return (false);
}
}
@@ -1981,13 +2078,13 @@ void remove_ring(void)
&& (hand_used == 0 || hand_used == 1))
{
mpr("You can't take your gloves off to remove any rings!");
- return;
+ return (false);
}
if (you.equip[hand_used + 7] == -1)
{
mpr("I don't think you really meant that.");
- return;
+ return (false);
}
if (item_cursed( you.inv[you.equip[hand_used + 7]] ))
@@ -1995,7 +2092,7 @@ void remove_ring(void)
mpr("It's stuck to you!");
set_ident_flags( you.inv[you.equip[hand_used + 7]], ISFLAG_KNOW_CURSE );
- return;
+ return (false);
}
strcpy(info, "You remove ");
@@ -2077,6 +2174,8 @@ void remove_ring(void)
calc_mp();
you.turn_is_over = 1;
+
+ return (true);
} // end remove_ring()
void zap_wand(void)
@@ -2639,12 +2738,12 @@ static bool enchant_armour( void )
{
if (item_cursed( item ))
{
- in_name(you.equip[affected], DESC_CAP_YOUR, str_pass);
+ item_name(item, DESC_CAP_YOUR, str_pass);
strcpy(info, str_pass);
strcat(info, " glows silver for a moment.");
mpr(info);
- do_uncurse_item( you.inv[you.equip[affected]] );
+ do_uncurse_item( item );
return (true);
}
else
@@ -2793,6 +2892,7 @@ void read_scroll(void)
if (you.conf)
{
random_uselessness(random2(9), item_slot);
+ dec_inv_item_quantity( item_slot, 1 );
return;
}
@@ -2835,7 +2935,7 @@ void read_scroll(void)
break;
case SCR_ACQUIREMENT:
- acquirement(OBJ_RANDOM);
+ acquirement(OBJ_RANDOM, AQ_SCROLL);
break;
case SCR_FEAR:
diff --git a/trunk/source/item_use.h b/trunk/source/item_use.h
index b5bed8cbc8..87c8e31455 100644
--- a/trunk/source/item_use.h
+++ b/trunk/source/item_use.h
@@ -49,7 +49,7 @@ void original_name(void);
/* ***********************************************************************
* called from: acr
* *********************************************************************** */
-void puton_ring(void);
+bool puton_ring(int slot = -1, bool prompt_finger = true);
// last updated 12may2000 {dlb}
@@ -63,7 +63,7 @@ void read_scroll(void);
/* ***********************************************************************
* called from: acr
* *********************************************************************** */
-void remove_ring(void);
+bool remove_ring(int slot = -1);
// last updated 12may2000 {dlb}
@@ -93,12 +93,18 @@ void wear_armour( void );
* *********************************************************************** */
bool do_wear_armour( int item, bool quiet );
+struct item_def;
+// last updated 30May2003 {ds}
+/* ***********************************************************************
+ * called from: food
+ * *********************************************************************** */
+bool can_wield(const item_def& weapon);
// last updated 12may2000 {dlb}
/* ***********************************************************************
* called from: acr
* *********************************************************************** */
-void wield_weapon(bool auto_wield);
+bool wield_weapon(bool auto_wield, int slot = -1, bool show_we_messages = true);
// last updated 12may2000 {dlb}
@@ -120,4 +126,8 @@ void wield_effects(int item_wield_2, bool showMsgs);
* *********************************************************************** */
void use_randart( unsigned char item_wield_2 );
+bool puton_item(int slot, bool prompt_finger = true);
+
+int armour_equip_slot(const item_def &item);
+
#endif
diff --git a/trunk/source/itemname.cc b/trunk/source/itemname.cc
index 5ca8a4d7f0..b92dcfc387 100644
--- a/trunk/source/itemname.cc
+++ b/trunk/source/itemname.cc
@@ -27,6 +27,7 @@
#include "externs.h"
+#include "invent.h"
#include "macro.h"
#include "mon-util.h"
#include "randart.h"
@@ -90,6 +91,13 @@ bool item_ident( const item_def &item, unsigned long flags )
return (item.flags & flags);
}
+bool item_type_known( const item_def &item )
+{
+ return item_ident(item, ISFLAG_KNOW_TYPE)
+ || (item.base_type == OBJ_JEWELLERY
+ && id[IDTYPE_JEWELLERY][item.sub_type] == ID_KNOWN_TYPE);
+}
+
bool item_not_ident( const item_def &item, unsigned long flags )
{
return ( !(item.flags & flags) );
@@ -391,7 +399,7 @@ char item_name( const item_def &item, char descrip, char buff[ ITEMNAME_SIZE ],
if (descrip == DESC_INVENTORY_EQUIP || descrip == DESC_INVENTORY)
{
- if (item.x == -1 && item.y == -1) // actually in inventory
+ if (in_inventory(item)) // actually in inventory
snprintf( buff, ITEMNAME_SIZE, (terse) ? "%c) " : "%c - ",
index_to_letter( item.link ) );
else
@@ -669,7 +677,7 @@ static char item_name_2( const item_def &item, char buff[ITEMNAME_SIZE],
(item.special == SPWPN_KNIFE_OF_ACCURACY) ? "thin dagger" :
(item.special == SPWPN_STAFF_OF_OLGREB) ? "green glowing staff" :
(item.special == SPWPN_VAMPIRES_TOOTH) ? "ivory dagger" :
- (item.special == SPWPN_STAFF_OF_WUCAD_MU) ? "quarterstaff"
+ (item.special == SPWPN_STAFF_OF_WUCAD_MU) ? "ephemeral quarterstaff"
: "buggy bola",
ITEMNAME_SIZE );
}
@@ -2042,6 +2050,52 @@ static char item_name_2( const item_def &item, char buff[ITEMNAME_SIZE],
strncat(buff, "!", ITEMNAME_SIZE );
} // end of switch?
+ // Disambiguation
+ if (!terse && item_ident(item, ISFLAG_KNOW_TYPE))
+ {
+#define name_append(x) strncat(buff, x, ITEMNAME_SIZE)
+ switch (item_clas)
+ {
+ case OBJ_STAVES:
+ switch (item_typ)
+ {
+ case STAFF_DESTRUCTION_I:
+ name_append(" [fire]");
+ break;
+ case STAFF_DESTRUCTION_II:
+ name_append(" [ice]");
+ break;
+ case STAFF_DESTRUCTION_III:
+ name_append(" [iron,fireball,lightning]");
+ break;
+ case STAFF_DESTRUCTION_IV:
+ name_append(" [inacc,magma,cold]");
+ break;
+ }
+ break;
+ case OBJ_BOOKS:
+ switch (item_typ)
+ {
+ case BOOK_MINOR_MAGIC_I:
+ name_append(" [flame]");
+ break;
+ case BOOK_MINOR_MAGIC_II:
+ name_append(" [frost]");
+ break;
+ case BOOK_MINOR_MAGIC_III:
+ name_append(" [summ]");
+ break;
+ case BOOK_CONJURATIONS_I:
+ name_append(" [fire]");
+ break;
+ case BOOK_CONJURATIONS_II:
+ name_append(" [ice]");
+ break;
+ }
+ }
+#undef name_append
+ }
+
// debugging output -- oops, I probably block it above ... dang! {dlb}
if (strlen(buff) < 3)
{
@@ -2787,7 +2841,11 @@ unsigned char check_item_knowledge(void)
yps = wherey();
// item_name now requires a "real" item, so we'll create a tmp
- item_def tmp = { ft, j, 0, 0, 0, 1, 0, 0, 0, 0, 0 };
+ item_def tmp;// = { ft, j, 0, 0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0 };
+ tmp.base_type = ft;
+ tmp.sub_type = j;
+ tmp.colour = 1;
+
item_name( tmp, DESC_PLAIN, st_pass );
cprintf(st_pass);
diff --git a/trunk/source/itemname.h b/trunk/source/itemname.h
index 91f649e641..0d1ccc79a2 100644
--- a/trunk/source/itemname.h
+++ b/trunk/source/itemname.h
@@ -87,6 +87,7 @@ bool item_known_uncursed( const item_def &item );
// bool fully_indentified( const item_def &item );
bool item_ident( const item_def &item, unsigned long flags );
+bool item_type_known( const item_def &item );
bool item_not_ident( const item_def &item, unsigned long flags );
void do_curse_item( item_def &item );
diff --git a/trunk/source/items.cc b/trunk/source/items.cc
index 30bd65a8cb..420948f3a4 100644
--- a/trunk/source/items.cc
+++ b/trunk/source/items.cc
@@ -18,10 +18,12 @@
#include "AppHdr.h"
#include "items.h"
+#include "clua.h"
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
+#include <ctype.h>
#ifdef DOS
#include <conio.h>
@@ -51,8 +53,10 @@
#include "skills.h"
#include "spl-cast.h"
#include "stuff.h"
+#include "stash.h"
static void autopickup(void);
+static bool is_stackable_item( const item_def &item );
// Used to be called "unlink_items", but all it really does is make
// sure item coordinates are correct to the stack they're in. -- bwr
@@ -461,6 +465,14 @@ void unlink_item( int dest )
#endif
} // end unlink_item()
+static void item_cleanup(item_def &item)
+{
+ item.base_type = OBJ_UNASSIGNED;
+ item.quantity = 0;
+ item.orig_place = 0;
+ item.orig_monnum = 0;
+}
+
void destroy_item( int dest )
{
// Don't destroy non-items, but this function may be called upon
@@ -471,8 +483,7 @@ void destroy_item( int dest )
unlink_item( dest );
- mitm[dest].base_type = OBJ_UNASSIGNED;
- mitm[dest].quantity = 0;
+ item_cleanup(mitm[dest]);
}
void destroy_item_stack( int x, int y )
@@ -533,7 +544,7 @@ void item_check(char keyin)
else if (grid >= DNGN_STONE_STAIRS_UP_I && grid <= DNGN_ROCK_STAIRS_UP)
{
snprintf( info, INFO_SIZE, "There is a %s staircase leading upwards here.",
- (grid == DNGN_ROCK_STAIRS_DOWN) ? "rock" : "stone" );
+ (grid == DNGN_ROCK_STAIRS_UP) ? "rock" : "stone" );
mpr(info);
}
@@ -708,6 +719,8 @@ void item_check(char keyin)
autopickup();
+ origin_set(you.x_pos, you.y_pos);
+
int objl = igrd[you.x_pos][you.y_pos];
while (objl != NON_ITEM)
@@ -773,11 +786,325 @@ void item_check(char keyin)
}
+void pickup_menu(int item_link)
+{
+ std::vector<item_def*> items;
+
+ for (int i = item_link; i != NON_ITEM; i = mitm[i].link)
+ items.push_back( &mitm[i] );
+
+ std::vector<SelItem> selected =
+ select_items( items, "Select items to pick up" );
+ redraw_screen();
+
+ for (int i = 0, count = selected.size(); i < count; ++i) {
+ for (int j = item_link; j != NON_ITEM; j = mitm[j].link) {
+ if (&mitm[j] == selected[i].item) {
+ if (j == item_link)
+ item_link = mitm[j].link;
+
+ unsigned long oldflags = mitm[j].flags;
+ mitm[j].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED);
+ int result = move_item_to_player( j, selected[i].quantity );
+
+ // If we cleared any flags on the items, but the pickup was
+ // partial, reset the flags for the items that remain on the
+ // floor.
+ if (is_valid_item(mitm[j]))
+ mitm[j].flags = oldflags;
+
+ if (result == 0)
+ {
+ mpr("You can't carry that much weight.");
+ return;
+ }
+ else if (result == -1)
+ {
+ mpr("You can't carry that many items.");
+ return;
+ }
+ break;
+ }
+ }
+ }
+}
+
+bool origin_known(const item_def &item)
+{
+ return (item.orig_place != 0);
+}
+
+// We have no idea where the player found this item.
+void origin_set_unknown(item_def &item)
+{
+ if (!origin_known(item))
+ {
+ item.orig_place = 0xFFFF;
+ item.orig_monnum = 0;
+ }
+}
+
+// This item is starting equipment.
+void origin_set_startequip(item_def &item)
+{
+ if (!origin_known(item))
+ {
+ item.orig_place = 0xFFFF;
+ item.orig_monnum = -1;
+ }
+}
+
+void origin_set_monster(item_def &item, const monsters *monster)
+{
+ if (!origin_known(item))
+ {
+ if (!item.orig_monnum)
+ item.orig_monnum = monster->type + 1;
+ item.orig_place = get_packed_place();
+ }
+}
+
+void origin_purchased(item_def &item)
+{
+ // We don't need to check origin_known if it's a shop purchase
+ item.orig_place = get_packed_place();
+ // Hackiness
+ item.orig_monnum = -1;
+}
+
+void origin_acquired(item_def &item, int agent)
+{
+ // We don't need to check origin_known if it's a divine gift
+ item.orig_place = get_packed_place();
+ // Hackiness
+ item.orig_monnum = -2 - agent;
+}
+
+void origin_set_inventory(void (*oset)(item_def &item))
+{
+ for (int i = 0; i < ENDOFPACK; ++i)
+ {
+ if (is_valid_item(you.inv[i]))
+ oset(you.inv[i]);
+ }
+}
+
+static int first_corpse_monnum(int x, int y)
+{
+ // We could look for a corpse on this square and assume that the
+ // items belonged to it, but that is unsatisfactory.
+ return (0);
+}
+
+void origin_set(int x, int y)
+{
+ int monnum = first_corpse_monnum(x, y);
+ unsigned short pplace = get_packed_place();
+ for (int link = igrd[x][y]; link != NON_ITEM; link = mitm[link].link)
+ {
+ item_def &item = mitm[link];
+ if (origin_known(item))
+ continue;
+ if (!item.orig_monnum)
+ item.orig_monnum = static_cast<short>( monnum );
+ item.orig_place = pplace;
+ }
+}
+
+void origin_set_monstercorpse(item_def &item, int x, int y)
+{
+ item.orig_monnum = first_corpse_monnum(x, y);
+}
+
+void origin_freeze(item_def &item, int x, int y)
+{
+ if (!origin_known(item))
+ {
+ if (!item.orig_monnum && x != -1 && y != -1)
+ origin_set_monstercorpse(item, x, y);
+ item.orig_place = get_packed_place();
+ }
+}
+
+static std::string origin_monster_name(const item_def &item)
+{
+ const int monnum = item.orig_monnum - 1;
+ if (monnum == MONS_PLAYER_GHOST)
+ return ("a player ghost");
+ else if (monnum == MONS_PANDEMONIUM_DEMON)
+ return ("a demon");
+ char monnamebuf[ITEMNAME_SIZE]; // Le sigh.
+ moname(monnum, true, DESC_NOCAP_A, monnamebuf);
+ return (monnamebuf);
+}
+
+static std::string origin_monster_desc(const item_def &item)
+{
+ return (origin_monster_name(item));
+}
+
+static std::string origin_place_desc(const item_def &item)
+{
+ std::string place = branch_level_name(item.orig_place);
+ if (place.length() && place != "Pandemonium")
+ place[0] = tolower(place[0]);
+ return (place.find("level") == 0?
+ "on " + place
+ : "in " + place);
+}
+
+bool is_rune(const item_def &item)
+{
+ return (item.base_type == OBJ_MISCELLANY &&
+ item.sub_type == MISC_RUNE_OF_ZOT);
+}
+
+bool origin_describable(const item_def &item)
+{
+ return (origin_known(item)
+ && (item.orig_place != 0xFFFFU || item.orig_monnum == -1)
+ && (!is_stackable_item(item) || is_rune(item))
+ && item.quantity == 1
+ && item.base_type != OBJ_CORPSES
+ && (item.base_type != OBJ_FOOD || item.sub_type != FOOD_CHUNK)
+ // Portable altars cannot be tracked meaningfully with Crawl's
+ // current handling for portable altars.
+ && (item.base_type != OBJ_MISCELLANY ||
+ item.sub_type != MISC_PORTABLE_ALTAR_OF_NEMELEX));
+}
+
+std::string article_it(const item_def &item)
+{
+ /*
+ bool them = false;
+ if (item.quantity > 1)
+ them = true;
+ else if (item.base_type == OBJ_ARMOUR &&
+ item.sub_type == ARM_BOOTS)
+ {
+ if (item.plus2 != TBOOT_NAGA_BARDING &&
+ item.plus2 != TBOOT_CENTAUR_BARDING)
+ them = true;
+ }
+ else if (item.base_type == OBJ_ARMOUR &&
+ item.sub_type == ARM_GLOVES)
+ {
+ them = true;
+ }
+
+ return them? "them" : "it";
+ */
+ // "it" is always correct, since gloves and boots also come in pairs.
+ return "it";
+}
+
+bool origin_is_original_equip(const item_def &item)
+{
+ return (item.orig_place == 0xFFFFU && item.orig_monnum == -1);
+}
+
+std::string origin_desc(const item_def &item)
+{
+ if (!origin_describable(item))
+ return ("");
+
+ if (origin_is_original_equip(item))
+ return "Original Equipment";
+
+ std::string desc;
+ if (item.orig_monnum)
+ {
+ if (item.orig_monnum < 0)
+ {
+ int iorig = -item.orig_monnum - 2;
+ switch (iorig)
+ {
+ case -1:
+ desc += "You bought " + article_it(item) + " in a shop ";
+ break;
+ case AQ_SCROLL:
+ desc += "You acquired " + article_it(item) + " ";
+ break;
+ case AQ_CARD_ACQUISITION:
+ desc += "You drew \"Acquisition\" ";
+ break;
+ case AQ_CARD_VIOLENCE:
+ desc += "You drew \"Violence\" ";
+ break;
+ case AQ_CARD_PROTECTION:
+ desc += "You drew \"Protection\" ";
+ break;
+ case AQ_CARD_KNOWLEDGE:
+ desc += "You drew \"Knowledge\" ";
+ break;
+ case AQ_WIZMODE:
+ desc += "Your wizardly powers created "
+ + article_it(item) + " ";
+ break;
+ default:
+ if (iorig > GOD_NO_GOD && iorig < NUM_GODS)
+ desc += std::string(god_name(iorig))
+ + " gifted " + article_it(item) + " to you ";
+ else
+ // Bug really.
+ desc += "You stumbled upon " + article_it(item) + " ";
+ break;
+ }
+ }
+ else if (item.orig_monnum - 1 == MONS_DANCING_WEAPON)
+ desc += "You subdued it ";
+ else
+ desc += "You took " + article_it(item) + " off "
+ + origin_monster_desc(item) + " ";
+ }
+ else
+ desc += "You found " + article_it(item) + " ";
+ desc += origin_place_desc(item);
+ return (desc);
+}
+
+bool pickup_single_item(int link, int qty)
+{
+ if (you.attribute[ATTR_TRANSFORMATION] == TRAN_AIR
+ && you.duration[DUR_TRANSFORMATION] > 0)
+ {
+ mpr("You can't pick up anything in this form!");
+ return (false);
+ }
+
+ if (player_is_levitating() && !wearing_amulet(AMU_CONTROLLED_FLIGHT))
+ {
+ mpr("You can't reach the floor from up here.");
+ return (false);
+ }
+
+ if (qty < 1 || qty > mitm[link].quantity)
+ qty = mitm[link].quantity;
+
+ unsigned long oldflags = mitm[link].flags;
+ mitm[link].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED);
+ int num = move_item_to_player( link, qty );
+ if (is_valid_item(mitm[link]))
+ mitm[link].flags = oldflags;
+
+ if (num == -1)
+ {
+ mpr("You can't carry that many items.");
+ return (false);
+ }
+ else if (num == 0)
+ {
+ mpr("You can't carry that much weight.");
+ return (false);
+ }
+
+ return (true);
+}
+
void pickup(void)
{
int o = 0;
int m = 0;
- int num = 0;
unsigned char keyin = 0;
int next;
char str_pass[ ITEMNAME_SIZE ];
@@ -846,12 +1173,7 @@ void pickup(void)
}
else if (mitm[o].link == NON_ITEM) // just one item?
{
- num = move_item_to_player( o, mitm[o].quantity );
-
- if (num == -1)
- mpr("You can't carry that many items.");
- else if (num == 0)
- mpr("You can't carry that much weight.");
+ pickup_single_item(o, mitm[o].quantity);
} // end of if items_here
else
{
@@ -881,17 +1203,24 @@ void pickup(void)
strcat(info, str_pass);
}
- strcat(info, "\? (y,n,a,q)");
+ strcat(info, "\? (y,n,a,*,q)");
mpr( info, MSGCH_PROMPT );
keyin = get_ch();
}
+ if (keyin == '*' || keyin == '?' || keyin == ',')
+ {
+ pickup_menu(o);
+ break;
+ }
+
if (keyin == 'q')
break;
if (keyin == 'y' || keyin == 'a')
{
+ mitm[o].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED);
int result = move_item_to_player( o, mitm[o].quantity );
if (result == 0)
@@ -984,6 +1313,75 @@ bool items_stack( const item_def &item1, const item_def &item2 )
return (true);
}
+static int userdef_find_free_slot(const item_def &i)
+{
+#ifdef CLUA_BINDINGS
+ int slot = -1;
+ if (!clua.callfn("c_assign_invletter", "u>d", &i, &slot))
+ return (-1);
+ return (slot);
+#else
+ return -1;
+#endif
+}
+
+int find_free_slot(const item_def &i)
+{
+#define slotisfree(s) \
+ ((s) >= 0 && (s) < ENDOFPACK && !is_valid_item(you.inv[s]))
+
+ bool searchforward = false;
+ // If we're doing Lua, see if there's a Lua function that can give
+ // us a free slot.
+ int slot = userdef_find_free_slot(i);
+ if (slot == -2 || Options.assign_item_slot == SS_FORWARD)
+ searchforward = true;
+
+ if (slotisfree(slot))
+ return slot;
+
+ // See if the item remembers where it's been. Lua code can play with
+ // this field so be extra careful.
+ if ((i.slot >= 'a' && i.slot <= 'z') ||
+ (i.slot >= 'A' && i.slot <= 'Z'))
+ slot = letter_to_index(i.slot);
+
+ if (slotisfree(slot))
+ return slot;
+
+ if (searchforward)
+ {
+ // Return first free slot
+ for (slot = 0; slot < ENDOFPACK; ++slot) {
+ if (!is_valid_item(you.inv[slot]))
+ return slot;
+ }
+ }
+ else
+ {
+ // This is the new default free slot search. We look for the last
+ // available slot that does not leave a gap in the inventory.
+ bool accept_empty = false;
+ for (slot = ENDOFPACK - 1; slot >= 0; --slot)
+ {
+ if (is_valid_item(you.inv[slot]))
+ {
+ if (!accept_empty && slot + 1 < ENDOFPACK &&
+ !is_valid_item(you.inv[slot + 1]))
+ return (slot + 1);
+
+ accept_empty = true;
+ }
+ else if (accept_empty)
+ {
+ return slot;
+ }
+ }
+ }
+ return (-1);
+#undef slotisfree
+}
+
// Returns quantity of items moved into player's inventory and -1 if
// the player's inventory is full.
int move_item_to_player( int obj, int quant_got, bool quiet )
@@ -1016,8 +1414,11 @@ int move_item_to_player( int obj, int quant_got, bool quiet )
}
unit_mass = mass_item( mitm[obj] );
- item_mass = unit_mass * mitm[obj].quantity;
- quant_got = mitm[obj].quantity;
+ if (quant_got > mitm[obj].quantity || quant_got <= 0)
+ quant_got = mitm[obj].quantity;
+
+ item_mass = unit_mass * quant_got;
+
brek = 0;
// multiply both constants * 10
@@ -1070,36 +1471,39 @@ int move_item_to_player( int obj, int quant_got, bool quiet )
if (!quiet && partialPickup)
mpr("You can only carry some of what is here.");
- for (m = 0; m < ENDOFPACK; m++)
+ int freeslot = find_free_slot(mitm[obj]);
+ if (freeslot < 0 || freeslot >= ENDOFPACK
+ || is_valid_item(you.inv[freeslot]))
{
- // find first empty slot
- if (!is_valid_item( you.inv[m] ))
- {
- // copy item
- you.inv[m] = mitm[obj];
- you.inv[m].x = -1;
- you.inv[m].y = -1;
- you.inv[m].link = m;
+ // Something is terribly wrong
+ return (-1);
+ }
- you.inv[m].quantity = quant_got;
- dec_mitm_item_quantity( obj, quant_got );
- burden_change();
+ item_def &item = you.inv[freeslot];
+ // copy item
+ item = mitm[obj];
+ item.x = -1;
+ item.y = -1;
+ item.link = freeslot;
- if (!quiet)
- {
- in_name( m, DESC_INVENTORY, info );
- mpr(info);
- }
+ origin_freeze(item, you.x_pos, you.y_pos);
- if (you.inv[m].base_type == OBJ_ORBS
- && you.char_direction == DIR_DESCENDING)
- {
- if (!quiet)
- mpr("Now all you have to do is get back out of the dungeon!");
- you.char_direction = DIR_ASCENDING;
- }
- break;
- }
+ item.quantity = quant_got;
+ dec_mitm_item_quantity( obj, quant_got );
+ burden_change();
+
+ if (!quiet)
+ {
+ in_name( freeslot, DESC_INVENTORY, info );
+ mpr(info);
+ }
+
+ if (item.base_type == OBJ_ORBS
+ && you.char_direction == DIR_DESCENDING)
+ {
+ if (!quiet)
+ mpr("Now all you have to do is get back out of the dungeon!");
+ you.char_direction = DIR_ASCENDING;
}
you.turn_is_over = 1;
@@ -1173,7 +1577,7 @@ void move_item_stack_to_grid( int x, int y, int targ_x, int targ_y )
// returns quantity dropped
bool copy_item_to_grid( const item_def &item, int x_plos, int y_plos,
- int quant_drop )
+ int quant_drop, bool mark_dropped )
{
if (quant_drop == 0)
return (false);
@@ -1190,6 +1594,11 @@ bool copy_item_to_grid( const item_def &item, int x_plos, int y_plos,
if (items_stack( item, mitm[i] ))
{
inc_mitm_item_quantity( i, quant_drop );
+
+ // If the items on the floor already have a nonzero slot,
+ // leave it as such, otherwise set the slot.
+ if (mark_dropped && !mitm[i].slot)
+ mitm[i].slot = index_to_letter(item.link);
return (true);
}
}
@@ -1209,6 +1618,14 @@ bool copy_item_to_grid( const item_def &item, int x_plos, int y_plos,
mitm[new_item].y = 0;
mitm[new_item].link = NON_ITEM;
+ if (mark_dropped)
+ {
+ mitm[new_item].slot = index_to_letter(item.link);
+ mitm[new_item].flags |= ISFLAG_DROPPED;
+ mitm[new_item].flags &= ~ISFLAG_THROWN;
+ origin_set_unknown(mitm[new_item]);
+ }
+
move_item_to_grid( &new_item, x_plos, y_plos );
return (true);
@@ -1260,6 +1677,7 @@ static void drop_gold(unsigned int amount)
inc_mitm_item_quantity( i, amount );
you.gold -= amount;
you.redraw_gold = 1;
+ you.turn_is_over = 1;
return;
}
@@ -1283,6 +1701,8 @@ static void drop_gold(unsigned int amount)
you.gold -= amount;
you.redraw_gold = 1;
+
+ you.turn_is_over = 1;
}
else
{
@@ -1290,46 +1710,15 @@ static void drop_gold(unsigned int amount)
}
} // end drop_gold()
-
-//---------------------------------------------------------------
-//
-// drop
-//
-// Prompts the user for an item to drop
-//
-//---------------------------------------------------------------
-void drop(void)
-{
- int i;
-
- int item_dropped;
- int quant_drop = -1;
-
- char str_pass[ ITEMNAME_SIZE ];
-
- if (inv_count() < 1 && you.gold == 0)
- {
- canned_msg(MSG_NOTHING_CARRIED);
- return;
- }
-
- // XXX: Need to handle quantities:
- item_dropped = prompt_invent_item( "Drop which item?", -1, true, true,
- true, '$', &quant_drop );
-
- if (item_dropped == PROMPT_ABORT || quant_drop == 0)
- {
- canned_msg( MSG_OK );
- return;
- }
- else if (item_dropped == PROMPT_GOT_SPECIAL) // ie '$' for gold
+bool drop_item( int item_dropped, int quant_drop ) {
+ if (item_dropped == PROMPT_GOT_SPECIAL) // ie '$' for gold
{
// drop gold
if (quant_drop < 0 || quant_drop > static_cast< int >( you.gold ))
quant_drop = you.gold;
drop_gold( quant_drop );
- return;
+ return (true);
}
if (quant_drop < 0 || quant_drop > you.inv[item_dropped].quantity)
@@ -1340,7 +1729,7 @@ void drop(void)
|| item_dropped == you.equip[EQ_AMULET])
{
mpr("You will have to take that off first.");
- return;
+ return (false);
}
if (item_dropped == you.equip[EQ_WEAPON]
@@ -1348,10 +1737,10 @@ void drop(void)
&& item_cursed( you.inv[item_dropped] ))
{
mpr("That object is stuck to you!");
- return;
+ return (false);
}
- for (i = EQ_CLOAK; i <= EQ_BODY_ARMOUR; i++)
+ for (int i = EQ_CLOAK; i <= EQ_BODY_ARMOUR; i++)
{
if (item_dropped == you.equip[i] && you.equip[i] != -1)
{
@@ -1372,7 +1761,7 @@ void drop(void)
// Regardless, we want to return here because either we're
// aborting the drop, or the drop is delayed until after
// the armour is removed. -- bwr
- return;
+ return (false);
}
}
@@ -1386,18 +1775,125 @@ void drop(void)
}
if (!copy_item_to_grid( you.inv[item_dropped],
- you.x_pos, you.y_pos, quant_drop ))
+ you.x_pos, you.y_pos, quant_drop, true ))
{
mpr( "Too many items on this level, not dropping the item." );
- return;
+ return (false);
}
+ char str_pass[ ITEMNAME_SIZE ];
quant_name( you.inv[item_dropped], quant_drop, DESC_NOCAP_A, str_pass );
snprintf( info, INFO_SIZE, "You drop %s.", str_pass );
mpr(info);
dec_inv_item_quantity( item_dropped, quant_drop );
you.turn_is_over = 1;
+
+ return (true);
+}
+
+
+static std::string drop_menu_title( int menuflags, const std::string &oldt ) {
+ std::string res = oldt;
+ res.erase(0, res.find_first_not_of(" \n\t"));
+ if (menuflags & MF_MULTISELECT)
+ res = "[Multidrop] " + res;
+ return " " + res;
+}
+
+int get_equip_slot(const item_def *item)
+{
+ int worn = -1;
+ if (item && in_inventory(*item))
+ {
+ for (int i = 0; i < NUM_EQUIP; ++i)
+ {
+ if (you.equip[i] == item->link)
+ {
+ worn = i;
+ break;
+ }
+ }
+ }
+ return worn;
+}
+
+static std::string drop_selitem_text( const std::vector<MenuEntry*> *s )
+{
+ char buf[130];
+ bool extraturns = false;
+
+ if (!s->size())
+ return "";
+
+ for (int i = 0, size = s->size(); i < size; ++i)
+ {
+ const item_def *item = static_cast<item_def *>( (*s)[i]->data );
+ int eq = get_equip_slot(item);
+ if (eq > EQ_WEAPON && eq < NUM_EQUIP)
+ {
+ extraturns = true;
+ break;
+ }
+ }
+
+ snprintf( buf, sizeof buf, " (%u%s turn%s)", s->size(),
+ extraturns? "+" : "",
+ s->size() > 1? "s" : "" );
+ return buf;
+}
+
+//---------------------------------------------------------------
+//
+// drop
+//
+// Prompts the user for an item to drop
+//
+//---------------------------------------------------------------
+void drop(void)
+{
+ if (inv_count() < 1 && you.gold == 0)
+ {
+ canned_msg(MSG_NOTHING_CARRIED);
+ you.activity = ACT_NONE;
+ return;
+ }
+
+ static std::vector<SelItem> selected;
+
+ if (!you.activity || selected.empty())
+ selected = prompt_invent_items( "Drop which item?", -1,
+ drop_menu_title,
+ true, true, '$',
+ &Options.drop_filter,
+ drop_selitem_text );
+
+ if (selected.empty())
+ {
+ canned_msg( MSG_OK );
+ you.activity = ACT_NONE;
+ return;
+ }
+
+ // Throw away invalid items; items usually go invalid because
+ // of chunks rotting away.
+ while (!selected.empty()
+ // Don't look for gold in inventory
+ && selected[0].slot != PROMPT_GOT_SPECIAL
+ && !is_valid_item(you.inv[ selected[0].slot ]))
+ selected.erase( selected.begin() );
+
+ // Did the defunct item filter above deprive us of a drop target?
+ if (!selected.empty())
+ {
+ drop_item( selected[0].slot, selected[0].quantity );
+ // Forget the item we just dropped
+ selected.erase( selected.begin() );
+ }
+
+ // If we still have items that want to be dropped, start the multiturn
+ // activity
+ you.activity = selected.empty()? ACT_NONE : ACT_MULTIDROP;
} // end drop()
//---------------------------------------------------------------
@@ -2415,6 +2911,18 @@ void handle_time( long time_delta )
int autopickup_on = 1;
+static bool is_banned(const item_def &item) {
+ static char name[ITEMNAME_SIZE];
+ item_name(item, DESC_INVENTORY, name, false);
+
+ std::string iname = name;
+ for (unsigned i = 0; i < Options.banned_objects.size(); ++i) {
+ if (Options.banned_objects[i].matches(iname))
+ return true;
+ }
+ return false;
+}
+
static void autopickup(void)
{
//David Loewenstern 6/99
@@ -2439,8 +2947,17 @@ static void autopickup(void)
{
next = mitm[o].link;
- if (Options.autopickups & (1L << mitm[o].base_type))
+ if ( ((mitm[o].flags & ISFLAG_THROWN) && Options.pickup_thrown) ||
+ ( (Options.autopickups & (1L << mitm[o].base_type)
+#ifdef CLUA_BINDINGS
+ || clua.callbooleanfn(false, "ch_autopickup", "u", &mitm[o])
+#endif
+ )
+ && (Options.pickup_dropped || !(mitm[o].flags & ISFLAG_DROPPED))
+ && !is_banned(mitm[o])))
{
+ mitm[o].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED);
+
result = move_item_to_player( o, mitm[o].quantity);
if (result == 0)
@@ -2468,7 +2985,7 @@ static void autopickup(void)
you.turn_is_over = 1;
start_delay( DELAY_AUTOPICKUP, 1 );
}
- }
+}
int inv_count(void)
{
diff --git a/trunk/source/items.h b/trunk/source/items.h
index 10b1ee27c9..c4bca72527 100644
--- a/trunk/source/items.h
+++ b/trunk/source/items.h
@@ -85,7 +85,8 @@ void pickup(void);
* called from: beam - items - transfor
* *********************************************************************** */
bool copy_item_to_grid( const item_def &item, int x_plos, int y_plos,
- int quant_drop = -1 ); // item.quantity by default
+ int quant_drop = -1, // item.quantity by default
+ bool mark_dropped = false);
// last updated Oct 15, 2000 -- bwr
/* ***********************************************************************
@@ -122,4 +123,22 @@ int inv_count(void);
void cmd_destroy_item( void );
+bool pickup_single_item(int link, int qty);
+
+bool drop_item( int item_dropped, int quant_drop );
+
+int get_equip_slot(const item_def *item);
+
+void origin_set(int x, int y);
+void origin_set_monster(item_def &item, const monsters *monster);
+void origin_freeze(item_def &item, int x, int y);
+bool origin_known(const item_def &item);
+bool origin_describable(const item_def &item);
+std::string origin_desc(const item_def &item);
+void origin_purchased(item_def &item);
+void origin_acquired(item_def &item, int agent);
+void origin_set_startequip(item_def &item);
+void origin_set_unknown(item_def &item);
+void origin_set_inventory( void (*oset)(item_def &item) );
+
#endif
diff --git a/trunk/source/libmac.h b/trunk/source/libmac.h
index 4f4dd992ef..8b7e3903d6 100644
--- a/trunk/source/libmac.h
+++ b/trunk/source/libmac.h
@@ -41,11 +41,6 @@ char* strlwr(char* str);
void itoa(int n, char* buffer, int radix);
#if !OSX
- inline int random()
- {
- return rand();
- }
-
inline void srandom(unsigned int seed)
{
srand(seed);
diff --git a/trunk/source/libunix.cc b/trunk/source/libunix.cc
index 9239ed5e99..72441dfd3d 100644
--- a/trunk/source/libunix.cc
+++ b/trunk/source/libunix.cc
@@ -74,7 +74,7 @@ static int key_to_command_table[KEY_MAX];
static unsigned int convert_to_curses_attr( int chattr )
{
- switch (chattr)
+ switch (chattr & CHATTR_ATTRMASK)
{
case CHATTR_STANDOUT: return (A_STANDOUT);
case CHATTR_BOLD: return (A_BOLD);
@@ -189,6 +189,23 @@ static void termio_init()
}
+int getch_ck() {
+ int c = getch();
+ switch (c) {
+ case KEY_BACKSPACE: return CK_BKSP;
+ case KEY_DC: return CK_DELETE;
+ case KEY_HOME: return CK_HOME;
+ case KEY_PPAGE: return CK_PGUP;
+ case KEY_END: return CK_END;
+ case KEY_NPAGE: return CK_PGDN;
+ case KEY_UP: return CK_UP;
+ case KEY_DOWN: return CK_DOWN;
+ case KEY_LEFT: return CK_LEFT;
+ case KEY_RIGHT: return CK_RIGHT;
+ default: return c;
+ }
+}
+
void init_key_to_command()
{
int i;
@@ -258,7 +275,7 @@ void init_key_to_command()
// control
key_to_command_table[ (int) CONTROL('A') ] = CMD_TOGGLE_AUTOPICKUP;
key_to_command_table[ (int) CONTROL('B') ] = CMD_OPEN_DOOR_DOWN_LEFT;
- key_to_command_table[ (int) CONTROL('C') ] = CMD_NO_CMD;
+ key_to_command_table[ (int) CONTROL('C') ] = CMD_CLEAR_MAP;
#ifdef ALLOW_DESTROY_ITEM_COMMAND
key_to_command_table[ (int) CONTROL('D') ] = CMD_DESTROY_ITEM;
@@ -266,9 +283,9 @@ void init_key_to_command()
key_to_command_table[ (int) CONTROL('D') ] = CMD_NO_CMD;
#endif
- key_to_command_table[ (int) CONTROL('E') ] = CMD_NO_CMD;
- key_to_command_table[ (int) CONTROL('F') ] = CMD_NO_CMD;
- key_to_command_table[ (int) CONTROL('G') ] = CMD_NO_CMD;
+ key_to_command_table[ (int) CONTROL('E') ] = CMD_FORGET_STASH;
+ key_to_command_table[ (int) CONTROL('F') ] = CMD_SEARCH_STASHES;
+ key_to_command_table[ (int) CONTROL('G') ] = CMD_INTERLEVEL_TRAVEL;
key_to_command_table[ (int) CONTROL('H') ] = CMD_OPEN_DOOR_LEFT;
key_to_command_table[ (int) CONTROL('I') ] = CMD_NO_CMD;
key_to_command_table[ (int) CONTROL('J') ] = CMD_OPEN_DOOR_DOWN;
@@ -276,15 +293,15 @@ void init_key_to_command()
key_to_command_table[ (int) CONTROL('L') ] = CMD_OPEN_DOOR_RIGHT;
key_to_command_table[ (int) CONTROL('M') ] = CMD_NO_CMD;
key_to_command_table[ (int) CONTROL('N') ] = CMD_OPEN_DOOR_DOWN_RIGHT;
- key_to_command_table[ (int) CONTROL('O') ] = CMD_NO_CMD;
+ key_to_command_table[ (int) CONTROL('O') ] = CMD_EXPLORE;
key_to_command_table[ (int) CONTROL('P') ] = CMD_REPLAY_MESSAGES;
key_to_command_table[ (int) CONTROL('Q') ] = CMD_NO_CMD;
key_to_command_table[ (int) CONTROL('R') ] = CMD_REDRAW_SCREEN;
- key_to_command_table[ (int) CONTROL('S') ] = CMD_NO_CMD;
+ key_to_command_table[ (int) CONTROL('S') ] = CMD_MARK_STASH;
key_to_command_table[ (int) CONTROL('T') ] = CMD_NO_CMD;
key_to_command_table[ (int) CONTROL('U') ] = CMD_OPEN_DOOR_UP_LEFT;
key_to_command_table[ (int) CONTROL('V') ] = CMD_NO_CMD;
- key_to_command_table[ (int) CONTROL('W') ] = CMD_NO_CMD;
+ key_to_command_table[ (int) CONTROL('W') ] = CMD_FIX_WAYPOINT;
key_to_command_table[ (int) CONTROL('X') ] = CMD_SAVE_GAME_NOW;
key_to_command_table[ (int) CONTROL('Y') ] = CMD_OPEN_DOOR_UP_RIGHT;
key_to_command_table[ (int) CONTROL('Z') ] = CMD_SUSPEND_GAME;
@@ -295,6 +312,7 @@ void init_key_to_command()
key_to_command_table[(int) '>'] = CMD_GO_DOWNSTAIRS;
key_to_command_table[(int) '@'] = CMD_DISPLAY_CHARACTER_STATUS;
key_to_command_table[(int) ','] = CMD_PICKUP;
+ key_to_command_table[(int) ':'] = CMD_NO_CMD;
key_to_command_table[(int) ';'] = CMD_INSPECT_FLOOR;
key_to_command_table[(int) '!'] = CMD_SHOUT;
key_to_command_table[(int) '^'] = CMD_DISPLAY_RELIGION;
@@ -322,6 +340,7 @@ void init_key_to_command()
key_to_command_table[(int) '\''] = CMD_WEAPON_SWAP;
// digits
+ key_to_command_table[(int) '0'] = CMD_NO_CMD;
key_to_command_table[(int) '1'] = CMD_MOVE_DOWN_LEFT;
key_to_command_table[(int) '2'] = CMD_MOVE_DOWN;
key_to_command_table[(int) '3'] = CMD_MOVE_DOWN_RIGHT;
@@ -358,11 +377,14 @@ void init_key_to_command()
// pass them through unmolested
key_to_command_table[128] = 128;
key_to_command_table[(int) '*'] = '*';
- key_to_command_table[(int) '/'] = '/';
}
int key_to_command(int keyin)
{
+ bool is_userfunction(int key);
+
+ if (is_userfunction(keyin))
+ return keyin;
return (key_to_command_table[keyin]);
}
@@ -383,11 +405,19 @@ void unixcurses_startup( void )
//savetty();
initscr();
- cbreak();
+ // cbreak();
+ raw();
noecho();
nonl();
intrflush(stdscr, FALSE);
+#ifdef CURSES_USE_KEYPAD
+ keypad(stdscr, TRUE);
+
+#if CURSES_SET_ESCDELAY
+ ESCDELAY = CURSES_SET_ESCDELAY;
+#endif
+#endif
//cbreak();
meta(stdscr, TRUE);
@@ -569,6 +599,14 @@ void _setcursortype(int curstype)
curs_set(curstype);
}
+inline unsigned get_brand(int col)
+{
+ return (col & COLFLAG_FRIENDLY_MONSTER)? Options.friend_brand :
+ (col & COLFLAG_ITEM_HEAP)? Options.heap_brand :
+ (col & COLFLAG_WILLSTAB)? Options.stab_brand :
+ (col & COLFLAG_MAYSTAB)? Options.may_stab_brand :
+ CHATTR_NORMAL;
+}
void textcolor(int col)
{
@@ -582,10 +620,17 @@ void textcolor(int col)
unsigned int flags = 0;
#ifdef USE_COLOUR_OPTS
- if ((col & COLFLAG_FRIENDLY_MONSTER)
- && Options.friend_brand != CHATTR_NORMAL)
+ unsigned brand = get_brand(col);
+ if (brand != CHATTR_NORMAL)
{
- flags |= convert_to_curses_attr( Options.friend_brand );
+ flags |= convert_to_curses_attr( brand );
+
+ if ((brand & CHATTR_ATTRMASK) == CHATTR_HILITE)
+ {
+ bg = (brand & CHATTR_COLMASK) >> 8;
+ if (fg == bg)
+ fg = COLOR_BLACK;
+ }
// If we can't do a dark grey friend brand, then we'll
// switch the colour to light grey.
@@ -630,11 +675,18 @@ void textbackground(int col)
unsigned int flags = 0;
#ifdef USE_COLOUR_OPTS
- if ((col & COLFLAG_FRIENDLY_MONSTER)
- && Options.friend_brand != CHATTR_NORMAL)
+ unsigned brand = get_brand(col);
+ if (brand != CHATTR_NORMAL)
{
- flags |= convert_to_curses_attr( Options.friend_brand );
+ flags |= convert_to_curses_attr( brand );
+ if ((brand & CHATTR_ATTRMASK) == CHATTR_HILITE)
+ {
+ bg = (brand & CHATTR_COLMASK) >> 8;
+ if (fg == bg)
+ fg = COLOR_BLACK;
+ }
+
// If we can't do a dark grey friend brand, then we'll
// switch the colour to light grey.
if (Options.no_dark_brand
diff --git a/trunk/source/libunix.h b/trunk/source/libunix.h
index f3370fc872..c416733666 100644
--- a/trunk/source/libunix.h
+++ b/trunk/source/libunix.h
@@ -11,7 +11,7 @@
char *strlwr(char *str);
char getche(void);
-
+int getch_ck(void);
int clrscr(void);
int cprintf(const char *format,...);
int gotoxy(int x, int y);
diff --git a/trunk/source/libutil.cc b/trunk/source/libutil.cc
index 5434eb3b0d..734ae2dc0a 100644
--- a/trunk/source/libutil.cc
+++ b/trunk/source/libutil.cc
@@ -12,10 +12,63 @@
*/
#include "AppHdr.h"
+#include "defines.h"
+#include "libutil.h"
+#include "externs.h"
#include <stdio.h>
#include <ctype.h>
+#include <stdarg.h>
#include <string.h>
+#ifdef WIN32CONSOLE
+ #include <windows.h>
+
+ #ifdef WINMM_PLAY_SOUNDS
+ #include <mmsystem.h>
+ #endif
+#endif
+
+#ifdef REGEX_PCRE
+ // Statically link pcre on Windows
+ #ifdef WIN32CONSOLE
+ #define PCRE_STATIC
+ #endif
+
+ #include <pcre.h>
+#endif
+
+#ifdef REGEX_POSIX
+ // Do we still need to include sys/types.h?
+ #include <sys/types.h>
+ #include <regex.h>
+#endif
+
+// Should return true if the filename contains nothing that
+// the shell can do damage with.
+bool shell_safe(const char *file)
+{
+ int match = strcspn(file, "`$*?|><");
+ return !(match >= 0 && file[match]);
+}
+
+void play_sound( const char *file )
+{
+#if defined(WINMM_PLAY_SOUNDS)
+ // Check whether file exists, is readable, etc.?
+ if (file && *file)
+ sndPlaySound(file, SND_ASYNC | SND_NODEFAULT);
+#elif defined(SOUND_PLAY_COMMAND)
+ char command[255];
+ command[0] = '\0';
+ if (file && *file && (strlen(file) + strlen(SOUND_PLAY_COMMAND) < 255)
+ && shell_safe(file))
+ {
+ snprintf(command, sizeof command, SOUND_PLAY_COMMAND, file);
+ system(command);
+ }
+#endif
+}
+
void get_input_line( char *const buff, int len )
{
buff[0] = '\0'; // just in case
@@ -45,6 +98,273 @@ void get_input_line( char *const buff, int len )
}
}
+#ifdef DOS
+static int getch_ck() {
+ int c = getch();
+ if (!c) {
+ switch (c = getch()) {
+ case 'O': return CK_END;
+ case 'P': return CK_DOWN;
+ case 'I': return CK_PGUP;
+ case 'H': return CK_UP;
+ case 'G': return CK_HOME;
+ case 'K': return CK_LEFT;
+ case 'Q': return CK_PGDN;
+ case 'M': return CK_RIGHT;
+ case 119: return CK_CTRL_HOME;
+ case 141: return CK_CTRL_UP;
+ case 132: return CK_CTRL_PGUP;
+ case 116: return CK_CTRL_RIGHT;
+ case 118: return CK_CTRL_PGDN;
+ case 145: return CK_CTRL_DOWN;
+ case 117: return CK_CTRL_END;
+ case 115: return CK_CTRL_LEFT;
+ case 'S': return CK_DELETE;
+ }
+ }
+ return c;
+}
+#endif
+
+// Hacky wrapper around getch() that returns CK_ codes for keys
+// we want to use in cancelable_get_line() and menus.
+int c_getch() {
+#if defined(DOS) || defined(LINUX) || defined(WIN32CONSOLE)
+ return getch_ck();
+#else
+ return getch();
+#endif
+}
+
+// cprintf that knows how to wrap down lines (primitive, but what the heck)
+int wrapcprintf( int wrapcol, const char *s, ... )
+{
+ char buf[1000]; // Hard max
+ va_list args;
+ va_start(args, s);
+
+ // XXX: If snprintf isn't available, vsnprintf probably isn't, either.
+ int len = vsnprintf(buf, sizeof buf, s, args), olen = len;
+ va_end(args);
+
+ char *run = buf;
+ while (len > 0)
+ {
+ int x = wherex(), y = wherey();
+
+ if (x > wrapcol) // Somebody messed up!
+ return 0;
+
+ int avail = wrapcol - x + 1;
+ int c = 0;
+ if (len > avail) {
+ c = run[avail];
+ run[avail] = 0;
+ }
+ cprintf("%s", run);
+
+ if (len > avail)
+ run[avail] = c;
+
+ if ((len -= avail) > 0)
+ gotoxy(1, y + 1);
+ run += avail;
+ }
+ return (olen);
+}
+
+#define WX(x) ( ((x) - 1) % maxcol + 1 )
+#define WY(x,y) ( (y) + ((x) - 1) / maxcol )
+#define WC(x,y) WX(x), WY(x,y)
+#define GOTOXY(x,y) gotoxy( WC(x,y) )
+bool cancelable_get_line( char *buf, int len, int maxcol,
+ input_history *mh )
+{
+ if (len <= 0) return false;
+
+ buf[0] = 0;
+
+ char *cur = buf;
+ int start = wherex(), line = wherey();
+ int length = 0, pos = 0;
+
+ if (mh)
+ mh->go_end();
+
+ for ( ; ; ) {
+ int ch = c_getch();
+
+ switch (ch) {
+ case CK_ESCAPE:
+ return false;
+ case CK_UP:
+ case CK_DOWN:
+ {
+ if (!mh)
+ break;
+ const std::string *text = ch == CK_UP? mh->prev() : mh->next();
+ if (text)
+ {
+ int olen = length;
+ length = text->length();
+ if (length >= len)
+ length = len - 1;
+ memcpy(buf, text->c_str(), length);
+ buf[length] = 0;
+ GOTOXY(start, line);
+
+ int clear = length < olen? olen - length : 0;
+ wrapcprintf(maxcol, "%s%*s", buf, clear, "");
+
+ pos = length;
+ cur = buf + pos;
+ GOTOXY(start + pos, line);
+ }
+ break;
+ }
+ case CK_ENTER:
+ buf[length] = 0;
+ if (mh && length)
+ mh->new_input(buf);
+ return true;
+ case CONTROL('K'):
+ {
+ // Kill to end of line
+ int erase = length - pos;
+ if (erase)
+ {
+ length = pos;
+ buf[length] = 0;
+ wrapcprintf( maxcol, "%*s", erase, "" );
+ GOTOXY(start + pos, line);
+ }
+ break;
+ }
+ case CK_DELETE:
+ if (pos < length) {
+ char *c = cur;
+ while (c - buf < length) {
+ *c = c[1];
+ c++;
+ }
+ --length;
+
+ GOTOXY( start + pos, line );
+ buf[length] = 0;
+ wrapcprintf( maxcol, "%s ", cur );
+ GOTOXY( start + pos, line );
+ }
+ break;
+ case CK_BKSP:
+ if (pos) {
+ --cur;
+ char *c = cur;
+ while (*c) {
+ *c = c[1];
+ c++;
+ }
+ --pos;
+ --length;
+
+ GOTOXY( start + pos, line );
+ buf[length] = 0;
+ wrapcprintf( maxcol, "%s ", cur );
+ GOTOXY( start + pos, line );
+ }
+ break;
+ case CK_LEFT:
+ if (pos) {
+ --pos;
+ cur = buf + pos;
+ GOTOXY( start + pos, line );
+ }
+ break;
+ case CK_RIGHT:
+ if (pos < length) {
+ ++pos;
+ cur = buf + pos;
+ GOTOXY( start + pos, line );
+ }
+ break;
+ case CK_HOME:
+ case CONTROL('A'):
+ pos = 0;
+ cur = buf + pos;
+ GOTOXY( start + pos, line );
+ break;
+ case CK_END:
+ case CONTROL('E'):
+ pos = length;
+ cur = buf + pos;
+ GOTOXY( start + pos, line );
+ break;
+ default:
+ if (isprint(ch) && length < len - 1) {
+ if (pos < length) {
+ char *c = buf + length - 1;
+ while (c >= cur) {
+ c[1] = *c;
+ c--;
+ }
+ }
+ *cur++ = (char) ch;
+ ++length;
+ ++pos;
+ putch(ch);
+ if (pos < length) {
+ buf[length] = 0;
+ wrapcprintf( maxcol, "%s", cur );
+ }
+ GOTOXY(start + pos, line);
+ }
+ break;
+ }
+ }
+}
+#undef GOTOXY
+#undef WC
+#undef WX
+#undef WY
+
+// also used with macros
+std::string & trim_string( std::string &str )
+{
+ // OK, this is really annoying. Borland C++ seems to define
+ // basic_string::erase to take iterators, and basic_string::remove
+ // to take size_t or integer. This is ass-backwards compared to
+ // nearly all other C++ compilers. Crap. (GDL)
+ //
+ // Borland 5.5 does this correctly now... leaving the old code
+ // around for now in case anyone needs it. -- bwr
+// #ifdef __BCPLUSPLUS__
+// str.remove( 0, str.find_first_not_of( " \t\n\r" ) );
+// str.remove( str.find_last_not_of( " \t\n\r" ) + 1 );
+// #else
+ str.erase( 0, str.find_first_not_of( " \t\n\r" ) );
+ str.erase( str.find_last_not_of( " \t\n\r" ) + 1 );
+// #endif
+
+ return (str);
+}
+
+std::vector<std::string> split_string(const char *sep, std::string s)
+{
+ std::vector<std::string> segments;
+
+ std::string::size_type pos;
+ while ((pos = s.find(sep, 0)) != std::string::npos) {
+ if (pos > 0)
+ segments.push_back(s.substr(0, pos));
+ s.erase(0, pos + 1);
+ }
+ if (s.length() > 0)
+ segments.push_back(s);
+
+ for (int i = 0, count = segments.size(); i < count; ++i)
+ trim_string(segments[i]);
+ return segments;
+}
+
// The old school way of doing short delays via low level I/O sync.
// Good for systems like old versions of Solaris that don't have usleep.
#ifdef NEED_USLEEP
@@ -72,7 +392,6 @@ void usleep(unsigned long time)
// and returns the number of characters that would have been written). -- bwr
#ifdef NEED_SNPRINTF
-#include <stdarg.h>
#include <string.h>
int snprintf( char *str, size_t size, const char *format, ... )
@@ -94,4 +413,318 @@ int snprintf( char *str, size_t size, const char *format, ... )
return (ret);
}
+
+#endif
+
+///////////////////////////////////////////////////////////////////////
+// Pattern matching
+
+inline int pm_lower(int ch, bool icase) {
+ return icase? tolower(ch) : ch;
+}
+
+// Determines whether the pattern specified by 'pattern' matches the given
+// text. A pattern is a simple glob, with the traditional * and ? wildcards.
+static bool glob_match( const char *pattern, const char *text, bool icase )
+{
+ char p, t;
+ bool special;
+
+ for (;;)
+ {
+ p = pm_lower(*pattern++, icase);
+ t = pm_lower(*text++, icase);
+ special = true;
+
+ if (!p) return t == 0;
+ if (p == '\\' && *pattern)
+ {
+ p = pm_lower(*pattern++, icase);
+ special = false;
+ }
+
+ if (p == '*' && special)
+ // Try to match exactly at the current text position...
+ return !*pattern || glob_match(pattern, text - 1, icase)? true :
+ // Or skip one character in the text and try the wildcard
+ // match again. If this is the end of the text, the match has
+ // failed.
+ t? glob_match(pattern - 1, text, icase) : false;
+ else if (!t || (p != t && (p != '?' || !special)))
+ return false;
+ }
+}
+
+#if defined(REGEX_PCRE)
+////////////////////////////////////////////////////////////////////
+// Perl Compatible Regular Expressions
+
+void *compile_pattern(const char *pattern, bool icase) {
+ const char *error;
+ int erroffset;
+ int flags = icase? PCRE_CASELESS : 0;
+ return pcre_compile(pattern,
+ flags,
+ &error,
+ &erroffset,
+ NULL);
+}
+
+void free_compiled_pattern(void *cp) {
+ if (cp)
+ pcre_free(cp);
+}
+
+bool pattern_match(void *compiled_pattern, const char *text, int length)
+{
+ int ovector[42];
+ int pcre_rc = pcre_exec(static_cast<pcre *>(compiled_pattern),
+ NULL,
+ text,
+ length,
+ 0,
+ 0,
+ ovector,
+ sizeof(ovector) / sizeof(*ovector));
+ return (pcre_rc >= 0);
+}
+
+////////////////////////////////////////////////////////////////////
+#elif defined(REGEX_POSIX)
+////////////////////////////////////////////////////////////////////
+// POSIX regular expressions
+
+void *compile_pattern(const char *pattern, bool icase) {
+ regex_t *re = new regex_t;
+ if (!re)
+ return NULL;
+
+ int flags = REG_EXTENDED | REG_NOSUB;
+ if (icase)
+ flags |= REG_ICASE;
+ int rc = regcomp(re, pattern, flags);
+ // Nonzero return code == failure
+ if (rc) {
+ delete re;
+ return NULL;
+ }
+ return re;
+}
+
+void free_compiled_pattern(void *cp) {
+ if (cp) {
+ regex_t *re = static_cast<regex_t *>( cp );
+ regfree(re);
+ delete re;
+ }
+}
+
+bool pattern_match(void *compiled_pattern, const char *text, int length)
+{
+ regex_t *re = static_cast<regex_t *>( compiled_pattern );
+ return !regexec(re, text, 0, NULL, 0);
+}
+
+////////////////////////////////////////////////////////////////////
+#else
+////////////////////////////////////////////////////////////////////
+// Basic glob
+
+struct glob_info
+{
+ std::string s;
+ bool ignore_case;
+};
+
+void *compile_pattern(const char *pattern, bool icase)
+{
+ // If we're using simple globs, we need to box the pattern with '*'
+ std::string s = std::string("*") + pattern + "*";
+ glob_info *gi = new glob_info;
+ if (gi) {
+ gi->s = s;
+ gi->ignore_case = icase;
+ }
+ return gi;
+}
+
+void free_compiled_pattern(void *compiled_pattern)
+{
+ delete static_cast<glob_info *>( compiled_pattern );
+}
+
+bool pattern_match(void *compiled_pattern, const char *text, int length)
+{
+ glob_info *gi = static_cast<glob_info *>( compiled_pattern );
+ return glob_match(gi->s.c_str(), text, gi->ignore_case);
+}
+////////////////////////////////////////////////////////////////////
+
#endif
+
+
+
+////////////////////////////////////////////////////////////////////
+// formatted_string
+//
+
+formatted_string::formatted_string(const std::string &s)
+ : ops()
+{
+ ops.push_back( s );
+}
+
+formatted_string::operator std::string() const
+{
+ std::string s;
+ for (int i = 0, size = ops.size(); i < size; ++i)
+ {
+ if (ops[i] == FSOP_TEXT)
+ s += ops[i].text;
+ }
+ return s;
+}
+
+inline void cap(int &i, int max)
+{
+ if (i < 0 && -i <= max)
+ i += max;
+ if (i >= max)
+ i = max - 1;
+ if (i < 0)
+ i = 0;
+}
+
+std::string formatted_string::tostring(int s, int e) const
+{
+ std::string st;
+
+ int size = ops.size();
+ cap(s, size);
+ cap(e, size);
+
+ for (int i = s; i <= e && i < size; ++i)
+ {
+ if (ops[i] == FSOP_TEXT)
+ st += ops[i].text;
+ }
+ return st;
+}
+
+void formatted_string::display(int s, int e) const
+{
+ int size = ops.size();
+ if (!size)
+ return ;
+
+ cap(s, size);
+ cap(e, size);
+
+ for (int i = s; i <= e && i < size; ++i)
+ ops[i].display();
+}
+
+void formatted_string::gotoxy(int x, int y)
+{
+ ops.push_back( fs_op(x, y) );
+}
+
+void formatted_string::textcolor(int color)
+{
+ ops.push_back(color);
+}
+
+void formatted_string::cprintf(const char *s, ...)
+{
+ char buf[1000];
+ va_list args;
+ va_start(args, s);
+ vsnprintf(buf, sizeof buf, s, args);
+ va_end(args);
+
+ cprintf(std::string(buf));
+}
+
+void formatted_string::cprintf(const std::string &s)
+{
+ ops.push_back(s);
+}
+
+void formatted_string::fs_op::display() const
+{
+ switch (type)
+ {
+ case FSOP_CURSOR:
+ {
+ int cx = (x == -1? wherex() : x);
+ int cy = (y == -1? wherey() : y);
+ ::gotoxy(cx, cy);
+ break;
+ }
+ case FSOP_COLOUR:
+ ::textcolor(x);
+ break;
+ case FSOP_TEXT:
+ ::cprintf("%s", text.c_str());
+ break;
+ }
+}
+
+/////////////////////////////////////////////////////////////
+// input_history
+//
+
+input_history::input_history(size_t size)
+ : history(), pos(), maxsize(size)
+{
+ if (maxsize < 2)
+ maxsize = 2;
+
+ pos = history.end();
+}
+
+void input_history::new_input(const std::string &s)
+{
+ history.remove(s);
+
+ if (history.size() == maxsize)
+ history.pop_front();
+
+ history.push_back(s);
+
+ // Force the iterator to the end (also revalidates it)
+ go_end();
+}
+
+const std::string *input_history::prev()
+{
+ if (history.empty())
+ return NULL;
+
+ if (pos == history.begin())
+ pos = history.end();
+
+ return &*--pos;
+}
+
+const std::string *input_history::next()
+{
+ if (history.empty())
+ return NULL;
+
+ if (pos == history.end() || ++pos == history.end())
+ pos = history.begin();
+
+ return &*pos;
+}
+
+void input_history::go_end()
+{
+ pos = history.end();
+}
+
+void input_history::clear()
+{
+ history.clear();
+ go_end();
+}
diff --git a/trunk/source/libutil.h b/trunk/source/libutil.h
index 73f8a849cf..bc05362604 100644
--- a/trunk/source/libutil.h
+++ b/trunk/source/libutil.h
@@ -11,8 +11,30 @@
#ifndef LIBUTIL_H
#define LIBUTIL_H
+#include <string>
+#include <vector>
+
+// getch() that returns a consistent set of values for all platforms.
+int c_getch();
+
+void play_sound(const char *file);
+
+// Pattern matching
+void *compile_pattern(const char *pattern, bool ignore_case = false);
+void free_compiled_pattern(void *cp);
+bool pattern_match(void *compiled_pattern, const char *text, int length);
+
void get_input_line( char *const buff, int len );
+class input_history;
+
+// Returns true if user pressed Enter, false if user hit Escape.
+bool cancelable_get_line( char *buf, int len, int wrapcol = 80,
+ input_history *mh = NULL );
+
+std::string & trim_string( std::string &str );
+std::vector<std::string> split_string(const char *sep, std::string s);
+
#ifdef NEED_USLEEP
void usleep( unsigned long time );
#endif
@@ -21,4 +43,175 @@ void usleep( unsigned long time );
int snprintf( char *str, size_t size, const char *format, ... );
#endif
+// Keys that getch() must return for keys Crawl is interested in.
+enum KEYS
+{
+ CK_ENTER = '\r',
+ CK_BKSP = 8,
+ CK_ESCAPE = '\x1b',
+
+ // 128 is off-limits because it's the code that's used when running
+ CK_DELETE = 129,
+
+ // This sequence of enums should not be rearranged.
+ CK_UP,
+ CK_DOWN,
+ CK_LEFT,
+ CK_RIGHT,
+
+ CK_INSERT,
+
+ CK_HOME,
+ CK_END,
+ CK_CLEAR,
+
+ CK_PGUP,
+ CK_PGDN,
+
+ CK_SHIFT_UP,
+ CK_SHIFT_DOWN,
+ CK_SHIFT_LEFT,
+ CK_SHIFT_RIGHT,
+
+ CK_SHIFT_INSERT,
+
+ CK_SHIFT_HOME,
+ CK_SHIFT_END,
+ CK_SHIFT_CLEAR,
+
+ CK_SHIFT_PGUP,
+ CK_SHIFT_PGDN,
+
+ CK_CTRL_UP,
+ CK_CTRL_DOWN,
+ CK_CTRL_LEFT,
+ CK_CTRL_RIGHT,
+
+ CK_CTRL_INSERT,
+
+ CK_CTRL_HOME,
+ CK_CTRL_END,
+ CK_CTRL_CLEAR,
+
+ CK_CTRL_PGUP,
+ CK_CTRL_PGDN,
+};
+
+class base_pattern
+{
+public:
+ virtual ~base_pattern() { };
+
+ virtual bool valid() const = 0;
+ virtual bool matches(const std::string &s) const = 0;
+};
+
+class text_pattern : public base_pattern
+{
+public:
+ text_pattern(const std::string &s, bool icase = false)
+ : pattern(s), compiled_pattern(NULL),
+ isvalid(true), ignore_case(icase)
+ {
+ }
+
+ text_pattern()
+ : pattern(), compiled_pattern(NULL),
+ isvalid(false), ignore_case(false)
+ {
+ }
+
+ text_pattern(const text_pattern &tp)
+ : pattern(tp.pattern),
+ compiled_pattern(NULL),
+ isvalid(tp.isvalid),
+ ignore_case(tp.ignore_case)
+ {
+ }
+
+ ~text_pattern()
+ {
+ if (compiled_pattern)
+ free_compiled_pattern(compiled_pattern);
+ }
+
+ const text_pattern &operator= (const text_pattern &tp)
+ {
+ if (this == &tp)
+ return tp;
+
+ if (compiled_pattern)
+ free_compiled_pattern(compiled_pattern);
+ pattern = tp.pattern;
+ compiled_pattern = NULL;
+ isvalid = tp.isvalid;
+ ignore_case = tp.ignore_case;
+ return *this;
+ }
+
+ const text_pattern &operator= (const std::string &spattern)
+ {
+ if (pattern == spattern)
+ return *this;
+
+ if (compiled_pattern)
+ free_compiled_pattern(compiled_pattern);
+ pattern = spattern;
+ compiled_pattern = NULL;
+ isvalid = true;
+ // We don't change ignore_case
+ return *this;
+ }
+
+ bool compile() const
+ {
+ // This function is const because compiled_pattern is not really part of
+ // the state of the object.
+
+ void *&cp = const_cast<text_pattern*>( this )->compiled_pattern;
+ return !empty()?
+ !!(cp = compile_pattern(pattern.c_str(), ignore_case))
+ : false;
+ }
+
+ bool empty() const
+ {
+ return !pattern.length();
+ }
+
+ bool valid() const
+ {
+ return isvalid &&
+ (compiled_pattern ||
+ (const_cast<text_pattern*>( this )->isvalid = compile()));
+ }
+
+ bool matches(const char *s, int length) const
+ {
+ return valid() && pattern_match(compiled_pattern, s, length);
+ }
+
+ bool matches(const char *s) const
+ {
+ return matches(std::string(s));
+ }
+
+ bool matches(const std::string &s) const
+ {
+ return matches(s.c_str(), s.length());
+ }
+
+ const std::string &tostring() const
+ {
+ return pattern;
+ }
+
+private:
+ std::string pattern;
+ void *compiled_pattern;
+ bool isvalid;
+ bool ignore_case;
+};
+
+
#endif
diff --git a/trunk/source/libw32c.cc b/trunk/source/libw32c.cc
index 26edc304f5..8426b82b53 100644
--- a/trunk/source/libw32c.cc
+++ b/trunk/source/libw32c.cc
@@ -47,7 +47,9 @@
#define NOSERVICE /* All Service Controller routines, SERVICE_ equates, etc. */
#define NOKANJI /* Kanji support stuff. */
#define NOMCX /* Modem Configuration Extensions */
+#ifndef _X86_
#define _X86_ /* target architecture */
+#endif
#include <excpt.h>
#include <stdarg.h>
@@ -68,6 +70,7 @@
#include "version.h"
#include "defines.h"
#include "view.h"
+#include "libutil.h"
char oldTitle[80];
@@ -87,14 +90,18 @@ static CHAR_INFO screen[80 * WIN_NUMBER_OF_LINES];
static COORD screensize;
#define SCREENINDEX(x,y) (x)+80*(y)
static bool buffering = false;
-static const char *windowTitle = "Crawl " VERSION;
+// static const char *windowTitle = "Crawl " VERSION;
+static unsigned InputCP, OutputCP;
+static const unsigned PREFERRED_CODEPAGE = 437;
// we can do straight translation of DOS color to win32 console color.
#define WIN32COLOR(col) (WORD)(col)
static void writeChar(char c);
static void bFlush(void);
static void _setcursortype_internal(int curstype);
-static void init_colors(void);
+
+// [ds] Unused for portability reasons
+/*
static DWORD crawlColorData[16] =
// BGR data, easier to put in registry
{
@@ -115,6 +122,7 @@ static DWORD crawlColorData[16] =
0x0000ffff, // YELLOW
0x00ffffff // WHITE
};
+ */
//#define TIMING_INFO
#ifdef TIMING_INFO
@@ -215,7 +223,7 @@ void writeChar(char c)
// is this a no-op?
if (pci->Char.AsciiChar != c)
noop = false;
- else if (pci->Attributes != tc && c != ' ')
+ else if (pci->Attributes != tc)
noop = false;
if (!noop)
@@ -287,7 +295,7 @@ void setStringInput(bool value)
}
else
{
- inmodes = NULL;
+ inmodes = 0;
outmodes = 0;
}
@@ -307,10 +315,9 @@ void setStringInput(bool value)
// this apparently only works for Win2K+ and ME+
-void init_colors(char *windowTitle)
+static void init_colors(char *windowTitle)
{
UNUSED( windowTitle );
- int i;
// look up the Crawl shortcut
@@ -357,6 +364,21 @@ void init_libw32c(void)
//DEBUG
//foo = fopen("debug.txt", "w");
+
+
+ // JWM, 06/12/2004: Code page setting, as XP does not use ANSI 437 by
+ // default.
+ InputCP = GetConsoleCP();
+ OutputCP = GetConsoleOutputCP();
+
+ // DS: Don't kill Crawl if we can't set the codepage. Windows 95/98/ME don't
+ // have support for setting the input and output codepage. I'm also not
+ // convinced we need to set the input codepage at all.
+ if (InputCP != PREFERRED_CODEPAGE)
+ SetConsoleCP(PREFERRED_CODEPAGE);
+
+ if (OutputCP != PREFERRED_CODEPAGE)
+ SetConsoleOutputCP(PREFERRED_CODEPAGE);
}
void deinit_libw32c(void)
@@ -365,6 +387,14 @@ void deinit_libw32c(void)
if (inbuf == NULL || outbuf == NULL)
return;
+ // JWM, 06/12/2004: Code page stuff. If it was the preferred code page, it
+ // doesn't need restoring. Shouldn't be an error and too bad if there is.
+ if (InputCP && InputCP != PREFERRED_CODEPAGE)
+ SetConsoleCP(InputCP);
+
+ if (OutputCP && OutputCP != PREFERRED_CODEPAGE)
+ SetConsoleOutputCP(OutputCP);
+
// restore console attributes for normal function
setStringInput(true);
@@ -553,24 +583,47 @@ void putch(char c)
// translate virtual keys
-static int vk_tr[4][9] = // virtual key, unmodified, shifted, control
+#define VKEY_MAPPINGS 10
+static int vk_tr[4][VKEY_MAPPINGS] = // virtual key, unmodified, shifted, control
{
- { VK_END, VK_DOWN, VK_NEXT, VK_LEFT, VK_CLEAR, VK_RIGHT, VK_HOME, VK_UP, VK_PRIOR },
- { 'b', 'j', 'n', 'h', '.', 'l', 'y', 'k', 'u' },
- { '1', '2', '3', '4', '5', '6', '7', '8', '9' },
- { 2, 10, 14, 8, 0, 12, 25, 11, 21 },
+ { VK_END, VK_DOWN, VK_NEXT, VK_LEFT, VK_CLEAR, VK_RIGHT, VK_HOME, VK_UP, VK_PRIOR, VK_INSERT },
+ { CK_END, CK_DOWN, CK_PGDN, CK_LEFT, CK_CLEAR, CK_RIGHT, CK_HOME, CK_UP, CK_PGUP , CK_INSERT },
+ { CK_SHIFT_END, CK_SHIFT_DOWN, CK_SHIFT_PGDN, CK_SHIFT_LEFT, CK_SHIFT_CLEAR, CK_SHIFT_RIGHT, CK_SHIFT_HOME, CK_SHIFT_UP, CK_SHIFT_PGUP, CK_SHIFT_INSERT },
+ { CK_CTRL_END, CK_CTRL_DOWN, CK_CTRL_PGDN, CK_CTRL_LEFT, CK_CTRL_CLEAR, CK_CTRL_RIGHT, CK_CTRL_HOME, CK_CTRL_UP, CK_CTRL_PGUP, CK_CTRL_INSERT },
};
+static int ck_tr[] = {
+ 'k', 'j', 'h', 'l', '0', 'y', 'b', '.', 'u', 'n',
+ // 'b', 'j', 'n', 'h', '.', 'l', 'y', 'k', 'u' ,
+ '8', '2', '4', '6', '0', '7', '1', '5', '9', '3',
+ // '1', '2', '3', '4', '5', '6', '7', '8', '9' ,
+ 11, 10, 8, 12, '0', 25, 2, 0, 21, 14
+ // 2, 10, 14, 8, 0, 12, 25, 11, 21 ,
+};
+
+int key_to_command( int keyin ) {
+ if (keyin >= CK_UP && keyin <= CK_CTRL_PGDN)
+ return ck_tr[ keyin - CK_UP ];
+
+ if (keyin == CK_DELETE)
+ return '.';
+
+ return keyin;
+}
+
int vk_translate( WORD VirtCode, CHAR c, DWORD cKeys)
{
bool shftDown = false;
bool ctrlDown = false;
+ bool altDown = !!(cKeys & (LEFT_ALT_PRESSED | RIGHT_ALT_PRESSED));
// DEBUG
//fprintf(foo, "Received code %d (%c) with modifiers: %d\n", VirtCode, c, cKeys);
// step 1 - we don't care about shift or control
- if (VirtCode == VK_SHIFT || VirtCode == VK_CONTROL)
+ if (VirtCode == VK_SHIFT || VirtCode == VK_CONTROL ||
+ VirtCode == VK_MENU || VirtCode == VK_CAPITAL ||
+ VirtCode == VK_NUMLOCK)
return 0;
// step 2 - translate the <Esc> key to 0x1b
@@ -581,37 +634,52 @@ int vk_translate( WORD VirtCode, CHAR c, DWORD cKeys)
if (cKeys & SHIFT_PRESSED)
shftDown = true;
if (cKeys & (RIGHT_CTRL_PRESSED | LEFT_CTRL_PRESSED))
- {
ctrlDown = true; // control takes precedence over shift
- shftDown = false;
- }
// hack - translate ^P and ^Q since 16 and 17 are taken by CTRL and SHIFT
if ((VirtCode == 80 || VirtCode == 81) && ctrlDown)
return VirtCode & 0x003f; // shift back down
if (VirtCode == VK_DELETE && !ctrlDown) // assume keypad '.'
- return '.';
+ return CK_DELETE;
// see if we're a vkey
int mkey;
- for(mkey = 0; mkey<9; mkey++)
+ for(mkey = 0; mkey<VKEY_MAPPINGS; mkey++)
if (VirtCode == vk_tr[0][mkey]) break;
// step 4 - just return the damn key.
- if (mkey == 9)
- return (int)c;
+ if (mkey == VKEY_MAPPINGS) {
+ if (c)
+ return c;
+
+ // ds -- Icky hacks to allow keymaps with funky keys.
+ if (ctrlDown)
+ VirtCode |= 512;
+ if (shftDown)
+ VirtCode |= 1024;
+ if (altDown)
+ VirtCode |= 2048;
+
+ // ds -- Cheat and returns 256 + VK if the char is zero. This allows us
+ // to use the VK for macros and is on par for evil with the rest of
+ // this function anyway.
+ return VirtCode | 256;
+ }
// now translate the key. Dammit. This is !@#$(*& garbage.
- if (shftDown)
- return vk_tr[2][mkey];
+
// control key?
if (ctrlDown)
return vk_tr[3][mkey];
+
+ // shifted?
+ if (shftDown)
+ return vk_tr[2][mkey];
return vk_tr[1][mkey];
}
-int getch(void)
+int getch_ck(void)
{
INPUT_RECORD ir;
DWORD nread;
@@ -665,6 +733,12 @@ int getch(void)
return key;
}
+int getch(void)
+{
+ int c = getch_ck();
+ return key_to_command( c );
+}
+
int getche(void)
{
// turn buffering off temporarily
@@ -684,8 +758,20 @@ int getche(void)
int kbhit()
{
- // do nothing. We could use PeekConsoleInput, I suppose..
- return 0;
+ INPUT_RECORD ir[10];
+ DWORD read_count = 0;
+ PeekConsoleInput(inbuf, ir, sizeof ir / sizeof(ir[0]), &read_count);
+ if (read_count > 0) {
+ for (unsigned i = 0; i < read_count; ++i)
+ if (ir[i].EventType == KEY_EVENT) {
+ KEY_EVENT_RECORD *kr;
+ kr = &(ir[i].Event.KeyEvent);
+
+ if (kr->bKeyDown)
+ return 1;
+ }
+ }
+ return 0;
}
void delay(int ms)
@@ -717,8 +803,7 @@ int getConsoleString(char *buf, int maxlen)
// terminate string, then strip CRLF, replace with \0
buf[maxlen-1] = '\0';
- int i;
- for (i=(nread<3 ? 0 : nread-3); i<nread; i++)
+ for (unsigned i=(nread<3 ? 0 : nread-3); i<nread; i++)
{
if (buf[i] == 0x0A || buf[i] == 0x0D)
{
diff --git a/trunk/source/libw32c.h b/trunk/source/libw32c.h
index 6a7cde1dd3..319efb02d8 100644
--- a/trunk/source/libw32c.h
+++ b/trunk/source/libw32c.h
@@ -33,13 +33,14 @@ int wherex(void);
int wherey(void);
void putch(char c);
int getch(void);
+int getch_ck(void);
+int key_to_command(int);
int getche(void);
int kbhit(void);
void delay(int ms);
void textbackground(int c);
inline void srandom(unsigned int seed) { srand(seed); }
-inline int random() { return rand(); }
#endif
diff --git a/trunk/source/lua/base.lua b/trunk/source/lua/base.lua
new file mode 100644
index 0000000000..db7b15afda
--- /dev/null
+++ b/trunk/source/lua/base.lua
@@ -0,0 +1,74 @@
+---------------------------------------------------------------------------
+-- base.lua:
+-- Base Lua definitions that other Lua scripts rely on.
+-- NOTE: Other Lua scripts may demonstrate buggy behaviour if you do
+-- not source this file. If you're using no Lua scripts at all, you
+-- needn't source base.lua.
+--
+-- To use this, add this line to your init.txt:
+-- lua_file = lua/base.lua
+---------------------------------------------------------------------------
+
+-- Lua global data
+chk_interrupt_macro = { }
+chk_interrupt_activity = { }
+chk_lua_option = { }
+
+-- Push into this table, rather than indexing into it.
+chk_lua_save = { }
+
+function c_save(file)
+ if not chk_lua_save then
+ return
+ end
+
+ for i, fn in ipairs(chk_lua_save) do
+ fn(file)
+ end
+end
+
+-- This function returns true to tell Crawl not to process the option further
+function c_process_lua_option(key, val)
+ if chk_lua_option and chk_lua_option[key] then
+ return chk_lua_option[key](key, val)
+ end
+ return false
+end
+
+function c_interrupt_macro(iname, cause, extra)
+ -- If some joker undefined the table, stop all macros
+ if not chk_interrupt_macro then
+ return true
+ end
+
+ -- Maybe we don't know what macro is running? Kill it to be safe
+ -- We also kill macros that don't add an entry to chk_interrupt_macro.
+ if not c_macro_name or not chk_interrupt_macro[c_macro_name] then
+ return true
+ end
+
+ return chk_interrupt_macro[c_macro_name](iname, cause, extra)
+end
+
+-- Notice that c_interrupt_activity defaults to *false* whereas
+-- c_interrupt_macro defaults to *true*. This is because "false" really just
+-- means "go ahead and use the default logic to kill this activity" here,
+-- whereas false is interpreted as "no, don't stop this macro" for
+-- c_interrupt_macro.
+--
+-- If c_interrupt_activity, or one of the individual hooks wants to ensure that
+-- the activity continues, it must return *nil* (make sure you know what you're
+-- doing when you return nil!).
+function c_interrupt_activity(aname, iname, cause, extra)
+ -- If some joker undefined the table, bail out
+ if not chk_interrupt_activity then
+ return false
+ end
+
+ -- No activity name? Bail!
+ if not aname or not chk_interrupt_activity[aname] then
+ return false
+ end
+
+ return chk_interrupt_activity[aname](iname, cause, extra)
+end
diff --git a/trunk/source/lua/chnkdata.lua b/trunk/source/lua/chnkdata.lua
new file mode 100644
index 0000000000..60f098c96b
--- /dev/null
+++ b/trunk/source/lua/chnkdata.lua
@@ -0,0 +1,35 @@
+-- SPOILER WARNING
+--
+-- This file contains spoiler information. Do not read or use this file if you
+-- don't want to be spoiled. Further, the Lua code in this file may use this
+-- spoily information to take actions on your behalf. If you don't want
+-- automatic exploitation of spoilers, don't use this.
+--
+---------------------------------------------------------------------------
+-- chnkdata.lua:
+-- Raw data on chunks of meat, auto-extracted from mon-data.h.
+--
+-- To use this, add this line to your init.txt:
+-- lua_file = lua/chnkdata.lua
+--
+-- This file has no directly usable functions, but is needed by gourmand.lua
+-- and enables auto_eat_chunks in eat.lua.
+---------------------------------------------------------------------------
+
+sc_cont = {"program bug","ettin","goblin","jackal","manticore","orc","ugly thing","two-headed ogre","hobgoblin","ogre","troll","orc warrior","orc wizard","orc knight","minotaur","beast","iron devil","orc sorcerer","","","hell knight","necromancer","wizard","orc priest","orc high priest","human","gnoll","earth elemental","fire elemental","air elemental","Ice Fiend","Shadow Fiend","spectral warrior","pulsating lump","rock troll","stone giant","flayed ghost","insubstantial wisp","vapour","ogre-mage","dancing weapon","elf","war dog","grey rat","giant mosquito","fire giant","frost giant","firedrake","deep troll","giant blowfly","swamp dragon","swamp drake","hill giant","giant cockroach","white imp","lemure","ufetubus","manes","midge","neqoxec","hellwing","smoke demon","ynoxinul","Executioner","Blue Death","Balrug","Cacodemon","demonic crawler","sun demon","shadow imp","shadow wraith","Mnoleg","Lom Lobon","Cerebov","Gloorx Vloq","orc warlord","deep elf soldier","deep elf fighter","deep elf knight","deep elf mage","deep elf summoner","deep elf conjurer","deep elf priest","deep elf high priest","deep elf demonologist","deep elf annihilator","deep elf sorcerer","deep elf death mage","Terence","Jessica","Ijyb","Sigmund","Blork the orc","Edmund","Psyche","Erolcha","Donald","Urug","Michael","Joseph","Snorg","Erica","Josephine","Harold","Norbert","Jozef","Agnes","Maud","Louise","Francis","Frances","Rupert","Wayne","Duane","Xtahua","Norris","Adolf","Margery","Boris","Geryon","Dispater","Asmodeus","Antaeus","Ereshkigal","vault guard","Killer Klown","ball lightning","orb of fire","boggart","quicksilver dragon","iron dragon","skeletal warrior","","&","warg","","","komodo dragon"}
+sc_pois = {"killer bee","killer bee larva","quasit","scorpion","yellow wasp","giant beetle","kobold","queen bee","kobold demonologist","big kobold","wolf spider","brain worm","boulder beetle","giant mite","hydra","mottled dragon","brown snake","death yak","bumblebee","redback","spiny worm","golden dragon","elephant slug","green rat","orange rat","black snake","giant centipede","iron troll","naga","yellow snake","red wasp","soldier ant","queen ant","ant larva","spiny frog","orange demon","Green Death","giant amoeba","giant slug","giant snail","boring beetle","naga mage","naga warrior","brown ooze","azure jelly","death ooze","acid blob","ooze","shining eye","greater naga","eye of devastation","gila monster"}
+sc_hcl = {"necrophage","ghoul"}
+sc_mut = {"guardian naga","eye of draining","giant orange brain","great orb of eyes","glowing shapeshifter","shapeshifter","very ugly thing"}
+
+function sc_to_hash(list)
+ local hash = { }
+ for _, i in ipairs(list) do
+ hash[i] = true
+ end
+ return hash
+end
+
+sc_cont = sc_to_hash( sc_cont )
+sc_pois = sc_to_hash( sc_pois )
+sc_hcl = sc_to_hash( sc_hcl )
+sc_mut = sc_to_hash( sc_mut )
diff --git a/trunk/source/lua/eat.lua b/trunk/source/lua/eat.lua
new file mode 100644
index 0000000000..2e5bda2432
--- /dev/null
+++ b/trunk/source/lua/eat.lua
@@ -0,0 +1,97 @@
+---------------------------------------------------------------------------
+-- eat.lua:
+-- Prompts to eat chunks from inventory.
+--
+-- To use this, add this line to your init.txt:
+-- lua_file = lua/eat.lua
+--
+-- See c_eat in this file if you want to tweak eating behaviour further.
+---------------------------------------------------------------------------
+function prompt_eat(i)
+ local iname = item.name(i, "a")
+ if item.quantity(i) > 1 then
+ iname = "one of " .. iname
+ end
+ crawl.mpr("Eat " .. iname .. "?", "prompt")
+
+ local res
+ res = crawl.getch()
+ if res == 27 then
+ res = "escape"
+ elseif res < 32 or res > 127 then
+ res = ""
+ else
+ res = string.lower(string.format("%c", res))
+ end
+ return res
+end
+
+function is_chunk_safe(chunk)
+ local rot = food.rotting(chunk)
+ local race = you.race()
+
+ -- Check if the user has sourced safechunk.lua and chnkdata.lua
+ if not (sc_cont and sc_pois and sc_hcl and sc_mut and sc_safechunk) then
+ return false
+ end
+
+ local cname = item.name(chunk)
+ local mon
+ _, _, mon = string.find(cname, "chunk of (.+) flesh")
+
+ return sc_safechunk(rot, race, mon)
+end
+
+-- Called by Crawl. Note that once Crawl sees a c_eat function, it bypasses the
+-- built-in (e)at command altogether.
+--
+function c_eat(floor, inv)
+ -- To enable auto_eat_chunks, you also need to source chnkdata.lua and
+ -- safechunk.lua. WARNING: These files contain spoily information.
+ local auto_eat_chunks = options.auto_eat_chunks == "yes" or
+ options.auto_eat_chunks == "true"
+
+ if auto_eat_chunks then
+ local all = { }
+ for _, it in ipairs(floor) do table.insert(all, it) end
+ for _, it in ipairs(inv) do table.insert(all, it) end
+
+ for _, it in ipairs(all) do
+ if food.ischunk(it) and food.can_eat(it) and is_chunk_safe(it) then
+ local iname = item.name(it, "a")
+ if item.quantity(it) > 1 then
+ iname = "one of " .. iname
+ end
+ crawl.mpr("Eating " .. iname)
+ food.eat(it)
+ return
+ end
+ end
+ end
+
+ -- Prompt to eat chunks off the floor. Returns true if the player chose
+ -- to eat a chunk.
+ if food.prompt_floor() then
+ return
+ end
+
+ for i, it in ipairs(inv) do
+ -- If we have chunks in inventory that the player can eat, prompt.
+ if food.ischunk(it) and food.can_eat(it) then
+ local answer = prompt_eat(it)
+ if answer == "q" then
+ break
+ end
+ if answer == "escape" then
+ return
+ end
+ if answer == "y" then
+ food.eat(it)
+ return
+ end
+ end
+ end
+
+ -- Allow the player to choose a snack from inventory
+ food.prompt_inventory()
+end
diff --git a/trunk/source/lua/gearset.lua b/trunk/source/lua/gearset.lua
new file mode 100644
index 0000000000..494ebd65ff
--- /dev/null
+++ b/trunk/source/lua/gearset.lua
@@ -0,0 +1,206 @@
+------------------------------------------------------------------------
+-- gearset.lua: (requires base.lua)
+-- Allows for quick switching between two sets of equipment.
+--
+-- IMPORTANT
+-- This Lua script remembers only the *inventory slots* of the gear you're
+-- wearing (it could remember item names, but they're prone to change based on
+-- identification, enchantment and curse status). If you swap around items in
+-- your inventory, the script may attempt to wear odd items (this will not kill
+-- the game, but it can be disconcerting if you're not expecting it).
+--
+-- To use this, source this file in your init.txt:
+-- lua_file = lua/gearset.lua
+--
+-- Then start Crawl and create two macros:
+-- 1. Any macro input key (say F2), set macro action to "===rememberkit"
+-- 2. Any macro input key (say F3), set macro action to "===swapkit"
+-- It helps to save your macros at this point. :-)
+--
+-- You can now hit F2 to remember the equipment you're wearing. Once you've
+-- defined two sets of equipment, hit F3 to swap between them.
+--
+-- You can also just define one set of equipment; in this case, every time
+-- you hit F3 (swapkit), the macro makes sure you're wearing all the items in
+-- that set (and only the items in that set). This is handy for transmuters who
+-- need to kit up after a transform ends.
+------------------------------------------------------------------------
+function scan_kit()
+ local kit = { }
+ for i = 9, 0, -1 do
+ local it = item.equipped_at(i)
+ if it then
+ table.insert(kit, item.slot(it))
+ end
+ end
+ return kit
+end
+
+function kit_items(kit)
+ local items = { }
+ local inv = item.inventory()
+
+ for _, slot in ipairs(kit) do
+ for _, it in ipairs(inv) do
+ if slot == item.slot(it) then
+ table.insert(items, it)
+ end
+ end
+ end
+ return items
+end
+
+function getkey(vkeys)
+ local key
+
+ while true do
+ key = crawl.getch()
+ if key == 27 then return "escape" end
+
+ if key > 31 and key < 127 then
+ local c = string.lower(string.char(key))
+ if string.find(vkeys, c) then
+ return c
+ end
+ end
+ end
+end
+
+-- Macroable
+function rememberkit()
+ local kit = scan_kit()
+ crawl.mpr("Is this (T)ravel or (B)attle kit? ", "prompt")
+ local answer = getkey("tb")
+ crawl.mesclr()
+ if answer == "escape" then
+ return false
+ end
+
+ if answer == 't' then
+ g_kit_travel = kit
+ else
+ g_kit_battle = kit
+ end
+
+ return false
+end
+
+function write_array(f, arr, aname)
+ file.write(f, aname .. " = { ")
+ for i, v in ipairs(arr) do
+ file.write(f, v .. ", ")
+ end
+ file.write(f, "}\n")
+end
+
+function gearset_save(file)
+ if g_kit_travel then
+ write_array(file, g_kit_travel, "g_kit_travel")
+ end
+ if g_kit_battle then
+ write_array(file, g_kit_battle, "g_kit_battle")
+ end
+end
+
+function matchkit(kit1, kit2)
+ local matches = 0
+ if not kit2 then
+ -- Return a positive match for an empty gearset so that swapkit
+ -- switches to the *other* gearset. :-)
+ return 500
+ end
+ for _, v1 in ipairs(kit1) do
+ for _, v2 in ipairs(kit2) do
+ if v1 == v2 then
+ matches = matches + 1
+ end
+ end
+ end
+ return matches
+end
+
+function wear(it)
+ local _, eqt = item.equip_type(it)
+ if eqt == "Weapon" then
+ return item.wield(it)
+ elseif eqt == "Amulet" or eqt == "Ring" then
+ return item.puton(it)
+ elseif eqt ~= "" then
+ return item.wear(it)
+ else
+ return false
+ end
+end
+
+function wearkit(kit)
+ -- Remove all items not in the kit, then wear all items in the kit
+ local currkit = scan_kit()
+ local toremove = { }
+ local noop = true
+
+ for _, v in ipairs(currkit) do
+ local found = false
+ for _, v1 in ipairs(kit) do
+ if v == v1 then
+ found = true
+ break
+ end
+ end
+ if not found then
+ table.insert(toremove, v)
+ end
+ end
+
+ local remitems = kit_items(toremove)
+ for _, it in ipairs(remitems) do
+ noop = false
+ if not item.remove(it) then
+ coroutine.yield(false)
+ end
+ coroutine.yield(true)
+ end
+
+ local kitems = kit_items(kit)
+
+ for _, it in ipairs(kitems) do
+ if not item.worn(it) then
+ noop = false
+ if not wear(it) then
+ coroutine.yield(false)
+ end
+ coroutine.yield(true)
+ end
+ end
+
+ if noop then
+ crawl.mpr("Nothing to do.")
+ end
+end
+
+-- Macroable
+function swapkit()
+ if not g_kit_battle and not g_kit_travel then
+ crawl.mpr("You need to define a gear set first")
+ return false
+ end
+
+ local kit = scan_kit()
+ if matchkit(kit, g_kit_travel) < matchkit(kit, g_kit_battle) then
+ crawl.mpr("Switching to travel kit")
+ wearkit(g_kit_travel)
+ else
+ crawl.mpr("Switching to battle kit")
+ wearkit(g_kit_battle)
+ end
+end
+
+function swapkit_interrupt_macro(interrupt_name)
+ return interrupt_name == "hp_loss" or interrupt_name == "stat" or
+ interrupt_name == "monster" or interrupt_name == "force"
+end
+
+-- Add a macro interrupt hook so that we don't get stopped by any old interrupt
+chk_interrupt_macro.swapkit = swapkit_interrupt_macro
+
+-- Add ourselves in the Lua save chain
+table.insert(chk_lua_save, gearset_save)
diff --git a/trunk/source/lua/gourmand.lua b/trunk/source/lua/gourmand.lua
new file mode 100644
index 0000000000..a730438419
--- /dev/null
+++ b/trunk/source/lua/gourmand.lua
@@ -0,0 +1,145 @@
+-- SPOILER WARNING
+--
+-- This file contains spoiler information. Do not read or use this file if you
+-- don't want to be spoiled. Further, the Lua code in this file may use this
+-- spoily information to take actions on your behalf. If you don't want
+-- automatic exploitation of spoilers, don't use this.
+--
+---------------------------------------------------------------------------
+-- gourmand.lua: (requires eat.lua, chnkdata.lua, and safechunk.lua)
+--
+-- Eats all available chunks on current square and inventory, swapping to an
+-- identified amulet of the gourmand if necessary, with no prompts. Note that
+-- it relies on spoiler information to identify chunks it can eat without
+-- prompting the user.
+--
+-- This is a Lua macro, so the action will be interrupted by the hp/stat loss,
+-- or monsters.
+--
+-- To use this, add these line to your init.txt:
+-- lua_file = lua/gourmand.lua
+--
+-- And macro the "eat_gourmand" function to a key.
+---------------------------------------------------------------------------
+-- Macroable
+function eat_gourmand()
+ local race = you.race()
+ local all = { }
+ for _, it in ipairs(you.floor_items()) do table.insert(all, it) end
+ for _, it in ipairs(item.inventory()) do table.insert(all, it) end
+
+ local chunks = { }
+ local needgourmand = false
+ local oneedible = false
+
+ for _, itym in ipairs(all) do
+ if food.ischunk(itym) then
+ table.insert(chunks, itym)
+ end
+ end
+
+ for _, itym in ipairs(chunks) do
+ local rot = food.rotting(itym)
+ local mon = chunkmon(itym)
+
+ if food.can_eat(itym) and sc_safechunk and
+ sc_safechunk(rot, race, mon) then
+ oneedible = true
+ end
+
+ -- If we can't eat it now, but we could eat it if hungry, a gourmand
+ -- switch would be nice.
+ if not food.can_eat(itym) and food.can_eat(itym, false) then
+ needgourmand = true
+ end
+
+ if sc_safechunk and not sc_safechunk(rot, race, mon)
+ and sc_safechunk(false, race, mon)
+ then
+ needgourmand = true
+ end
+ end
+
+ if table.getn(chunks) == 0 then
+ crawl.mpr("No chunks to eat.")
+ return
+ end
+
+ local amuremoved
+ if needgourmand and not oneedible then
+ amuremoved = switch_amulet("gourmand")
+ end
+
+ for _, ch in ipairs(chunks) do
+ if food.can_eat(ch) and is_chunk_safe(ch) then
+ while item.quantity(ch) > 0 do
+ if food.eat(ch) then
+ coroutine.yield(true)
+ else
+ break
+ end
+ end
+ end
+ end
+
+ if amuremoved then
+ switch_amulet(amuremoved)
+ end
+end
+
+function chunkmon(chunk)
+ local cname = item.name(chunk)
+ local mon
+ _, _, mon = string.find(cname, "chunk of (.+) flesh")
+ return mon
+end
+
+function switch_amulet(amu)
+ local towear
+ if type(amu) == "string" then
+ for _, it in ipairs(item.inventory()) do
+ if item.class(it, true) == "jewelry"
+ and item.subtype(it) == amu
+ and not item.cursed(it) then
+ towear = item.slot(it)
+ break
+ end
+ end
+
+ if not towear then
+ crawl.mpr("Can't find suitable amulet: " .. amu)
+ coroutine.yield(false)
+ end
+ else
+ towear = amu
+ end
+
+ local curramu = item.equipped_at("amulet")
+ if curramu and item.cursed(curramu) then
+ crawl.mpr("Can't swap out cursed amulet!")
+ coroutine.yield(false)
+ end
+
+ local wearitem = item.inslot(towear)
+
+ if curramu then
+ if not item.remove(curramu) then
+ coroutine.yield(false)
+ end
+
+ -- Yield, so that a turn can pass and we can take another action.
+ coroutine.yield(true)
+ end
+
+ if not item.puton(wearitem) then
+ coroutine.yield(false)
+ end
+
+ coroutine.yield(true)
+ return curramu and item.slot(curramu)
+end
+
+function c_interrupt_macro(interrupt_name)
+ return interrupt_name == "hp_loss" or interrupt_name == "stat" or
+ interrupt_name == "monster" or interrupt_name == "force"
+end
diff --git a/trunk/source/lua/kills.lua b/trunk/source/lua/kills.lua
new file mode 100644
index 0000000000..b84b1f93c0
--- /dev/null
+++ b/trunk/source/lua/kills.lua
@@ -0,0 +1,240 @@
+------------------------------------------------------------------
+-- kills.lua:
+-- Generates fancier vanquished creatures lists.
+--
+-- List ideas courtesy Jukka Kuusisto and Erik Piper.
+--
+-- To use this, add this line to your init.txt:
+-- lua_file = lua/kills.lua
+--
+-- Take a look at the c_kill_list function - that's where to start if you
+-- want to customize things.
+------------------------------------------------------------------
+
+function kill_filter(a, condition)
+ local t = { }
+ for i, k in ipairs(a) do
+ if condition(k) then
+ table.insert(t, k)
+ end
+ end
+ return t
+end
+
+function show_sorted_list(list, baseindent, sortfn)
+ baseindent = baseindent or " "
+ sortfn = sortfn or
+ function (x, y)
+ return kills.exp(x) > kills.exp(y) or
+ (kills.exp(x) == kills.exp(y) and
+ kills.base_name(x) < kills.base_name(y))
+ end
+ table.sort(list, sortfn)
+ for i, k in ipairs(list) do
+ kills.rawwrite(baseindent .. " " .. kills.desc(k))
+ end
+ kills.rawwrite(baseindent .. kills.summary(list))
+end
+
+function group_kills(a, namemap, keys, selector)
+ local count = 0
+ for i, key in ipairs(keys) do
+ local selected = kill_filter(a,
+ function (k)
+ return selector(key, k)
+ end
+ )
+ if table.getn(selected) > 0 then
+ if count > 0 then
+ kills.rawwrite("")
+ end
+ count = count + 1
+ kills.rawwrite(" " .. namemap[key])
+ show_sorted_list(selected)
+ end
+ end
+end
+
+function holiness_list(a)
+ local holies = {
+ strange = "Strange Monsters",
+ unique = "Uniques",
+ holy = "Holy Monsters",
+ natural = "Natural Monsters",
+ undead = "Undead Monsters",
+ demonic = "Demonic Monsters",
+ nonliving = "Non-Living Monsters",
+ plant = "Plants",
+ }
+ local holysort = { "strange", "unique",
+ "natural", "nonliving",
+ "undead", "demonic", "plant" }
+ kills.rawwrite(" Monster Nature")
+ group_kills( a, holies, holysort,
+ function ( key, kill )
+ return (kills.holiness(kill) == key and not kills.isunique(kill))
+ or
+ (key == "unique" and kills.isunique(kill))
+ end
+ )
+ kills.rawwrite(" " .. kills.summary(a))
+end
+
+function count_list(a, ascending)
+ kills.rawwrite(" Ascending Order")
+ show_sorted_list(a,
+ " ",
+ function (x, y)
+ if ascending then
+ return kills.nkills(x) < kills.nkills(y)
+ or (kills.nkills(x) == kills.nkills(y) and
+ kills.exp(x) > kills.exp(y))
+ else
+ return kills.nkill(x) > kills.nkills(y)
+ or (kills.nkills(x) == kills.nkills(y) and
+ kills.exp(x) > kills.exp(y))
+ end
+ end
+ )
+end
+
+function symbol_list(a)
+ -- Create a list of monster characters and sort it
+ local allsyms = { }
+ for i, k in ipairs(a) do
+ local ksym = kills.symbol(k)
+ allsyms[ kills.symbol(k) ] = true
+ end
+
+ local sortedsyms = { }
+ for sym, dud in pairs(allsyms) do table.insert(sortedsyms, sym) end
+ table.sort(sortedsyms)
+
+ kills.rawwrite(" Monster Symbol")
+ for i, sym in ipairs(sortedsyms) do
+ if i > 1 then
+ kills.rawwrite("")
+ end
+
+ local symlist = kill_filter(a,
+ function (k)
+ return kills.symbol(k) == sym
+ end
+ )
+ kills.rawwrite(" All '" .. sym .. "'")
+ show_sorted_list(symlist)
+ end
+
+ kills.rawwrite(" " .. kills.summary(a))
+end
+
+function classic_list(title, a, suffix)
+ if table.getn(a) == 0 then return end
+ kills.rawwrite(title)
+ for i, k in ipairs(a) do
+ kills.rawwrite(" " .. kills.desc(k))
+ end
+ kills.rawwrite(" " .. kills.summary(a))
+ if suffix then
+ kills.rawwrite(suffix)
+ end
+end
+
+function separator()
+ kills.rawwrite("----------------------------------------\n")
+end
+
+function newline()
+ kills.rawwrite("")
+end
+
+-----------------------------------------------------------------------------
+-- This is the function that Crawl calls when it wants to dump the kill list
+-- The parameter "a" is the list (Lua table) of kills, initially sorted in
+-- descending order of experience. Kill entries must be inspected using
+-- kills.foo(ke).
+--
+-- NOTE: Comment out the kill lists that you don't like!
+--
+-- As of 20060224, the kill list is divided into three:
+-- * Direct player kills
+-- * Monsters killed by friendlies
+-- * Monsters killed by other things (traps, etc.)
+--
+-- For each category, the game calls c_kill_list, with a table of killed
+-- monsters, and the killer (who). The killer will be nil for direct player
+-- kills, and a string ("collateral kills", "others") otherwise.
+--
+-- c_kill_list will not be called for a category if the category contains no
+-- kills.
+--
+-- After all categories, c_kill_list will be called with no arguments to
+-- indicate the end of the kill listing.
+
+function c_kill_list(a, who, needsep)
+ -- If there aren't any kills yet, bail out
+ if not a or table.getn(a) == 0 then
+ if not a and who and who ~= "" then
+ -- This is the grand total
+ separator()
+ kills.rawwrite(who)
+ end
+ return
+ end
+
+ if needsep then
+ separator()
+ end
+
+ local title = "Vanquished Creatures"
+ if who then
+ title = title .. " (" .. who .. ")"
+ end
+
+ kills.rawwrite(title)
+
+ -- Group kills by monster symbol
+ symbol_list(a)
+ newline()
+
+ -- Group by holiness
+ holiness_list(a)
+ newline()
+
+ -- Sort by kill count (ascending).
+ count_list(a, true)
+ newline()
+
+ -- Classic list without zombies and skeletons
+ -- Commented-out - this is a boring list.
+ --[[
+ -- First re-sort into descending order of exp, since
+ -- count_list has re-sorted the array.
+ table.sort(a, function (x, y)
+ return kills.exp(x) > kills.exp(y)
+ end);
+
+ -- Filter out zombies and skeletons and display the rest
+ classic_list(
+ " Kills minus zombies and skeletons",
+ kill_filter(a,
+ function (k)
+ return kills.modifier(k) ~= "zombie" and
+ kills.modifier(k) ~= "skeleton"
+ end
+ ))
+
+ separator()
+ --]]
+
+ -- Show only monsters with 3 digit kill count
+ classic_list(
+ " The 3-digit Club",
+ kill_filter(a,
+ function (k)
+ return kills.nkills(k) > 99
+ end
+ ),
+ "")
+
+end
diff --git a/trunk/source/lua/runrest.lua b/trunk/source/lua/runrest.lua
new file mode 100644
index 0000000000..6f5536506e
--- /dev/null
+++ b/trunk/source/lua/runrest.lua
@@ -0,0 +1,108 @@
+---------------------------------------------------------------------------
+-- runrest.lua: (requires base.lua)
+-- Controls shift-running and resting stop conditions.
+--
+-- To use this, add this line to your init.txt:
+-- lua_file = lua/runrest.lua
+--
+-- What it does:
+--
+-- * Any message in runrest_ignore_message will *not* stop your run.
+-- * Poison damage of x will be ignored if you have at least y hp if you've
+-- defined a runrest_ignore_poison = x:y option.
+--
+-- IMPORTANT: You must define runrest_ options *after* sourcing runrest.lua.
+---------------------------------------------------------------------------
+
+g_rr_ignored = { }
+
+chk_interrupt_activity.run = function (iname, cause, extra)
+ if not rr_check_params() then
+ return false
+ end
+
+ if iname == 'message' then
+ return rr_handle_message(cause, extra)
+ end
+
+ if iname == 'hp_loss' then
+ return rr_handle_hploss(cause, extra)
+ end
+
+ return false
+end
+
+function rr_handle_message(cause, extra)
+ local ch, mess = rr_split_channel(cause)
+ for _, m in ipairs(g_rr_ignored) do
+ if m:matches(mess, ch) then
+ return nil
+ end
+ end
+ return false
+end
+
+function rr_split_channel(s)
+ local chi = string.find(s, ':')
+ local channel = -1
+ if chi and chi > 1 then
+ local chstr = string.sub(s, 1, chi - 1)
+ channel = crawl.msgch_num(chstr)
+ end
+
+ local sor = s
+ if chi then
+ s = string.sub(s, chi + 1, -1)
+ end
+
+ return channel, s
+end
+
+function rr_handle_hploss(hplost, source)
+ -- source == 1 for poisoning
+ if not g_rr_yhpmin or not g_rr_hplmax or source ~= 1 then
+ return false
+ end
+
+ -- If the hp lost is smaller than configured, and you have more than the
+ -- minimum health, ignore this poison event.
+ if hplost <= g_rr_hplmax and you.hp() >= g_rr_yhpmin then
+ return nil
+ end
+
+ return false
+end
+
+function rr_check_params()
+ if g_rrim ~= options.runrest_ignore_message then
+ g_rrim = options.runrest_ignore_message
+ rr_add_messages(nil, g_rrim)
+ end
+
+ if ( not g_rr_hplmax or not g_rr_yhpmin )
+ and options.runrest_ignore_poison
+ then
+ local opt = options.runrest_ignore_poison
+ local hpl, hpm
+ _, _, hpl, hpm = string.find(opt, "(%d+)%s*:%s*(%d+)")
+ if hpl and hpm then
+ g_rr_hplmax = tonumber(hpl)
+ g_rr_yhpmin = tonumber(hpm)
+ end
+ end
+ return true
+end
+
+function rr_add_message(s)
+ local channel, str = rr_split_channel(s)
+ table.insert( g_rr_ignored, crawl.message_filter( str, channel ) )
+end
+
+function rr_add_messages(key, value)
+ local segs = crawl.split(value, ',')
+ for _, s in ipairs(segs) do
+ rr_add_message(s)
+ end
+end
+
+chk_lua_option.runrest_ignore_message = rr_add_messages
diff --git a/trunk/source/lua/safechunk.lua b/trunk/source/lua/safechunk.lua
new file mode 100644
index 0000000000..cd0c9f036d
--- /dev/null
+++ b/trunk/source/lua/safechunk.lua
@@ -0,0 +1,41 @@
+-- SPOILER WARNING
+--
+-- This file contains spoiler information. Do not read or use this file if you
+-- don't want to be spoiled. Further, the Lua code in this file may use this
+-- spoily information to take actions on your behalf. If you don't want
+-- automatic exploitation of spoilers, don't use this.
+--
+---------------------------------------------------------------------------
+-- safechunk.lua:
+-- Determines whether a chunk of meat is safe for your character to eat.
+--
+-- To use this, add this line to your init.txt:
+-- lua_file = lua/safechunk.lua
+--
+-- This file has no directly usable functions, but is needed by gourmand.lua
+-- and enables auto_eat_chunks in eat.lua.
+---------------------------------------------------------------------------
+
+function sc_safechunk(rot, race, mon)
+ if race == "Ghoul" then
+ return true
+ end
+
+ if rot then
+ if race ~= "Kobold" and race ~= "Troll" and not you.gourmand() then
+ return false
+ end
+ end
+
+ if sc_pois[mon] and you.res_poison() > 0 then
+ return true
+ end
+
+ if sc_hcl[mon] or sc_mut[mon] then
+ return false
+ end
+
+ -- Only contaminated and clean chunks remain, in theory. We'll accept
+ -- either
+ return true
+end
diff --git a/trunk/source/lua/stash.lua b/trunk/source/lua/stash.lua
new file mode 100644
index 0000000000..461a504cf3
--- /dev/null
+++ b/trunk/source/lua/stash.lua
@@ -0,0 +1,32 @@
+---------------------------------------------------------------------------
+-- stash.lua
+-- Annotates items for the stash-tracker.
+--
+-- To use this, add this line to your init.txt:
+-- lua_file = lua/stash.lua
+--
+---------------------------------------------------------------------------
+
+-- Annotate items for searches
+function ch_stash_search_annotate_item(it)
+ local annot = ""
+
+ if item.artifact(it) then
+ annot = annot .. "{art} "
+ elseif item.branded(it) then
+ annot = annot .. "{ego} "
+ elseif item.class(it, true) == "book" then
+ annot = annot .. "{book} "
+ end
+
+ local skill = item.weap_skill(it)
+ if skill then
+ annot = annot .. "{" .. skill .. "} "
+ end
+
+ return annot
+end
+
+--- If you want dumps (.lst files) to be annotated, uncomment this line:
+-- ch_stash_dump_annotate_item = ch_stash_search_annotate_item
+
diff --git a/trunk/source/lua/wield.lua b/trunk/source/lua/wield.lua
new file mode 100644
index 0000000000..e4fe9951e3
--- /dev/null
+++ b/trunk/source/lua/wield.lua
@@ -0,0 +1,47 @@
+---------------------------------------------------------------------------
+-- wield.lua
+-- Selects extra items to wield.
+--
+-- To use this, add this line to your init.txt:
+-- lua_file = lua/wield.lua
+---------------------------------------------------------------------------
+
+function make_hash(ls)
+ local h = { }
+ for _, i in ipairs(ls) do
+ h[i] = true
+ end
+ return h
+end
+
+function ch_item_wieldable(it)
+ -- We only need to check for unusual cases - basic matching is handled
+ -- by Crawl itself.
+ local spells = make_hash( you.spells() )
+
+ if spells["Bone Shards"]
+ and string.find( item.name(it, "a"), " skeleton" )
+ then
+ return true
+ end
+
+ if (spells["Sublimation of Blood"] or spells["Simulacrum"])
+ and food.ischunk(it)
+ then
+ return true
+ end
+
+ if spells["Sandblast"]
+ and string.find( item.name(it, "a"), " stones?$" )
+ then
+ return true
+ end
+
+ if spells["Sticks to Snakes"]
+ and string.find( item.name(it, "a"), " arrows?$" )
+ then
+ return true
+ end
+
+ return false
+end
diff --git a/trunk/source/macro.cc b/trunk/source/macro.cc
index 4cd5c62ede..13838a947c 100644
--- a/trunk/source/macro.cc
+++ b/trunk/source/macro.cc
@@ -37,11 +37,13 @@
#include <string>
#include <map>
#include <deque>
+#include <vector>
#include <stdio.h> // for snprintf
#include <ctype.h> // for tolower
#include "externs.h"
+#include "stuff.h"
// for trim_string:
#include "initfile.h"
@@ -50,11 +52,117 @@ typedef std::deque<int> keyseq;
typedef std::deque<int> keybuf;
typedef std::map<keyseq,keyseq> macromap;
-static macromap Keymaps;
+static macromap Keymaps[KC_CONTEXT_COUNT];
static macromap Macros;
+static macromap *all_maps[] =
+{
+ &Keymaps[KC_DEFAULT],
+ &Keymaps[KC_LEVELMAP],
+ &Keymaps[KC_TARGETING],
+ &Macros,
+};
+
static keybuf Buffer;
-
+
+#define USERFUNCBASE -10000
+static std::vector<std::string> userfunctions;
+
+inline int userfunc_index(int key)
+{
+ int index = (key <= USERFUNCBASE? USERFUNCBASE - key : -1);
+ return (index < 0 || index >= (int) userfunctions.size()? -1 : index);
+}
+
+static int userfunc_index(const keyseq &seq)
+{
+ if (seq.empty())
+ return (-1);
+
+ return userfunc_index(seq.front());
+}
+
+bool is_userfunction(int key)
+{
+ return (userfunc_index(key) != -1);
+}
+
+static bool is_userfunction(const keyseq &seq)
+{
+ return (userfunc_index(seq) != -1);
+}
+
+const char *get_userfunction(int key)
+{
+ int index = userfunc_index(key);
+ return (index == -1? NULL : userfunctions[index].c_str());
+}
+
+static const char *get_userfunction(const keyseq &seq)
+{
+ int index = userfunc_index(seq);
+ return (index == -1? NULL : userfunctions[index].c_str());
+}
+
+static bool userfunc_referenced(int index, const macromap &mm)
+{
+ for (macromap::const_iterator i = mm.begin(); i != mm.end(); i++)
+ {
+ if (userfunc_index(i->second) == index)
+ return (true);
+ }
+ return (false);
+}
+
+static bool userfunc_referenced(int index)
+{
+ for (unsigned i = 0; i < sizeof(all_maps) / sizeof(*all_maps); ++i)
+ {
+ macromap *m = all_maps[i];
+ if (userfunc_referenced(index, *m))
+ return (true);
+ }
+ return (false);
+}
+
+// Expensive function to discard unused function names
+static void userfunc_collectgarbage(void)
+{
+ for (int i = userfunctions.size() - 1; i >= 0; --i)
+ {
+ if (!userfunctions.empty() && !userfunc_referenced(i))
+ userfunctions[i].clear();
+ }
+}
+
+static int userfunc_getindex(const std::string &fname)
+{
+ if (fname.length() == 0)
+ return (-1);
+
+ userfunc_collectgarbage();
+
+ // Pass 1 to see if we have this string already
+ for (int i = 0, count = userfunctions.size(); i < count; ++i)
+ {
+ if (userfunctions[i] == fname)
+ return (i);
+ }
+
+ // Pass 2 to hunt for gaps
+ for (int i = 0, count = userfunctions.size(); i < count; ++i)
+ {
+ if (userfunctions[i].empty())
+ {
+ userfunctions[i] = fname;
+ return (i);
+ }
+ }
+
+ userfunctions.push_back(fname);
+ return (userfunctions.size() - 1);
+}
+
/*
* Returns the name of the file that contains macros.
*/
@@ -68,6 +176,29 @@ static std::string get_macro_file()
return (s + "macro.txt");
}
+static void buf2keyseq(const char *buff, keyseq &k)
+{
+ // Sanity check
+ if (!buff)
+ return;
+
+ // convert c_str to keyseq
+
+ // Check for user functions
+ if (*buff == '=' && buff[1] == '=' && buff[2] == '=' && buff[3])
+ {
+ int index = userfunc_getindex(buff + 3);
+ if (index != -1)
+ k.push_back( USERFUNCBASE - index );
+ }
+ else
+ {
+ const int len = strlen( buff );
+ for (int i = 0; i < len; i++)
+ k.push_back( buff[i] );
+ }
+}
+
/*
* Takes as argument a string, and returns a sequence of keys described
* by the string. Most characters produce their own ASCII code. There
@@ -81,41 +212,47 @@ static keyseq parse_keyseq( std::string s )
keyseq v;
int num;
+ if (s.find("===") == 0)
+ {
+ buf2keyseq(s.c_str(), v);
+ return (v);
+ }
+
for (std::string::iterator i = s.begin(); i != s.end(); i++)
{
- char c = *i;
-
- switch (state)
- {
- case 0: // Normal state
- if (c == '\\') {
- state = 1;
- } else {
- v.push_back(c);
- }
- break;
-
- case 1: // Last char is a '\'
- if (c == '\\') {
- state = 0;
- v.push_back(c);
- } else if (c == '{') {
- state = 2;
- num = 0;
- }
- // XXX Error handling
- break;
-
- case 2: // Inside \{}
- if (c == '}') {
- v.push_back(num);
- state = 0;
- } else if (c >= '0' && c <= '9') {
- num = num * 10 + c - '0';
- }
- // XXX Error handling
+ char c = *i;
+
+ switch (state)
+ {
+ case 0: // Normal state
+ if (c == '\\') {
+ state = 1;
+ } else {
+ v.push_back(c);
+ }
+ break;
+
+ case 1: // Last char is a '\'
+ if (c == '\\') {
+ state = 0;
+ v.push_back(c);
+ } else if (c == '{') {
+ state = 2;
+ num = 0;
+ }
+ // XXX Error handling
break;
- }
+
+ case 2: // Inside \{}
+ if (c == '}') {
+ v.push_back(num);
+ state = 0;
+ } else if (c >= '0' && c <= '9') {
+ num = num * 10 + c - '0';
+ }
+ // XXX Error handling
+ break;
+ }
}
return (v);
@@ -125,13 +262,25 @@ static keyseq parse_keyseq( std::string s )
* Serializes a key sequence into a string of the format described
* above.
*/
-static std::string vtostr( keyseq v )
+static std::string vtostr( const keyseq &seq )
{
std::string s;
+
+ const keyseq *v = &seq;
+ keyseq dummy;
+ if (is_userfunction(seq))
+ {
+ // Laboriously reconstruct the original user input
+ std::string newseq = std::string("==") + get_userfunction(seq);
+ buf2keyseq(newseq.c_str(), dummy);
+ dummy.push_front('=');
+
+ v = &dummy;
+ }
- for (keyseq::iterator i = v.begin(); i != v.end(); i++)
+ for (keyseq::const_iterator i = v->begin(); i != v->end(); i++)
{
- if (*i < 32 || *i > 127) {
+ if (*i <= 32 || *i > 127) {
char buff[10];
snprintf( buff, sizeof(buff), "\\{%d}", *i );
@@ -154,13 +303,13 @@ static std::string vtostr( keyseq v )
// in libutil.cc to make sure of that! -- bwr
//
// std::ostrstream ss;
- // ss << "\\{" << *i << "}" << ends;
- // s += ss.str();
- } else if (*i == '\\') {
- s += "\\\\";
- } else {
- s += *i;
- }
+ // ss << "\\{" << *i << "}" << ends;
+ // s += ss.str();
+ } else if (*i == '\\') {
+ s += "\\\\";
+ } else {
+ s += *i;
+ }
}
return (s);
@@ -174,6 +323,14 @@ static void macro_add( macromap &mapref, keyseq key, keyseq action )
mapref[key] = action;
}
+static void macro_add( macromap &mapref, keyseq key, const char *buff )
+{
+ keyseq act;
+ buf2keyseq(buff, act);
+ if (!act.empty())
+ macro_add( mapref, key, act );
+}
+
/*
* Remove a macro.
*/
@@ -190,15 +347,15 @@ static void macro_del( macromap &mapref, keyseq key )
static void macro_buf_add( keyseq actions )
{
for (keyseq::iterator i = actions.begin(); i != actions.end(); i++)
- Buffer.push_back(*i);
+ Buffer.push_back(*i);
}
/*
* Adds a single keypress into the internal keybuffer.
*/
-static void macro_buf_add( int key )
+void macro_buf_add( int key )
{
- Buffer.push_back( key );
+ Buffer.push_back( key );
}
@@ -206,7 +363,7 @@ static void macro_buf_add( int key )
* Adds keypresses from a sequence into the internal keybuffer. Does some
* O(N^2) analysis to the sequence to replace macros.
*/
-static void macro_buf_add_long( keyseq actions )
+static void macro_buf_add_long( keyseq actions, macromap &keymap = Keymaps[KC_DEFAULT] )
{
keyseq tmp;
@@ -221,36 +378,36 @@ static void macro_buf_add_long( keyseq actions )
while (actions.size() > 0)
{
- tmp = actions;
-
- while (tmp.size() > 0)
+ tmp = actions;
+
+ while (tmp.size() > 0)
{
- keyseq result = Keymaps[tmp];
-
- // Found a macro. Add the expansion (action) of the
- // macro into the buffer.
- if (result.size() > 0) {
- macro_buf_add( result );
- break;
- }
-
- // Didn't find a macro. Remove a key from the end
- // of the sequence, and try again.
- tmp.pop_back();
- }
-
- if (tmp.size() == 0) {
- // Didn't find a macro. Add the first keypress of the sequence
- // into the buffer, remove it from the sequence, and try again.
- macro_buf_add( actions.front() );
- actions.pop_front();
-
- } else {
- // Found a macro, which has already been added above. Now just
- // remove the macroed keys from the sequence.
- for (unsigned int i = 0; i < tmp.size(); i++)
- actions.pop_front();
- }
+ keyseq result = keymap[tmp];
+
+ // Found a macro. Add the expansion (action) of the
+ // macro into the buffer.
+ if (result.size() > 0) {
+ macro_buf_add( result );
+ break;
+ }
+
+ // Didn't find a macro. Remove a key from the end
+ // of the sequence, and try again.
+ tmp.pop_back();
+ }
+
+ if (tmp.size() == 0) {
+ // Didn't find a macro. Add the first keypress of the sequence
+ // into the buffer, remove it from the sequence, and try again.
+ macro_buf_add( actions.front() );
+ actions.pop_front();
+
+ } else {
+ // Found a macro, which has already been added above. Now just
+ // remove the macroed keys from the sequence.
+ for (unsigned int i = 0; i < tmp.size(); i++)
+ actions.pop_front();
+ }
}
}
@@ -275,7 +432,7 @@ static void macro_buf_apply_command_macro( void )
// Add macro to front:
for (keyseq::reverse_iterator k = result.rbegin(); k != result.rend(); k++)
- Buffer.push_front(*k);
+ Buffer.push_front(*k);
break;
}
@@ -299,6 +456,20 @@ static int macro_buf_get( void )
return (key);
}
+static void write_map(std::ofstream &f, const macromap &mp, const char *key)
+{
+ for (macromap::const_iterator i = mp.begin(); i != mp.end(); i++)
+ {
+ // Need this check, since empty values are added into the
+ // macro struct for all used keyboard commands.
+ if (i->second.size())
+ {
+ f << key << vtostr((*i).first) << std::endl
+ << "A:" << vtostr((*i).second) << std::endl << std::endl;
+ }
+ }
+}
+
/*
* Saves macros into the macrofile, overwriting the old one.
*/
@@ -309,30 +480,15 @@ void macro_save( void )
f << "# WARNING: This file is entirely auto-generated." << std::endl
<< std::endl << "# Key Mappings:" << std::endl;
-
- for (macromap::iterator i = Keymaps.begin(); i != Keymaps.end(); i++)
- {
- // Need this check, since empty values are added into the
- // macro struct for all used keyboard commands.
- if ((*i).second.size() > 0)
- {
- f << "K:" << vtostr((*i).first) << std::endl
- << "A:" << vtostr((*i).second) << std::endl << std::endl;
- }
+ for (int mc = KC_DEFAULT; mc < KC_CONTEXT_COUNT; ++mc) {
+ char keybuf[30] = "K:";
+ if (mc)
+ snprintf(keybuf, sizeof keybuf, "K%d:", mc);
+ write_map(f, Keymaps[mc], keybuf);
}
f << "# Command Macros:" << std::endl;
-
- for (macromap::iterator i = Macros.begin(); i != Macros.end(); i++)
- {
- // Need this check, since empty values are added into the
- // macro struct for all used keyboard commands.
- if ((*i).second.size() > 0)
- {
- f << "M:" << vtostr((*i).first) << std::endl
- << "A:" << vtostr((*i).second) << std::endl << std::endl;
- }
- }
+ write_map(f, Macros, "M:");
f.close();
}
@@ -341,17 +497,20 @@ void macro_save( void )
* Reads as many keypresses as are available (waiting for at least one),
* and returns them as a single keyseq.
*/
-static keyseq getch_mul( void )
+static keyseq getch_mul( int (*rgetch)() = NULL )
{
keyseq keys;
int a;
- keys.push_back( a = getch() );
+ if (!rgetch)
+ rgetch = getch;
+
+ keys.push_back( a = rgetch() );
// The a == 0 test is legacy code that I don't dare to remove. I
// have a vague recollection of it being a kludge for conio support.
while ((kbhit() || a == 0)) {
- keys.push_back( a = getch() );
+ keys.push_back( a = rgetch() );
}
return (keys);
@@ -361,7 +520,12 @@ static keyseq getch_mul( void )
* Replacement for getch(). Returns keys from the key buffer if available.
* If not, adds some content to the buffer, and returns some of it.
*/
-int getchm( void )
+int getchm( int (*rgetch)() )
+{
+ return getchm( KC_DEFAULT, rgetch );
+}
+
+int getchm(KeymapContext mc, int (*rgetch)())
{
int a;
@@ -370,9 +534,9 @@ int getchm( void )
return (a);
// Read some keys...
- keyseq keys = getch_mul();
+ keyseq keys = getch_mul(rgetch);
// ... and add them into the buffer
- macro_buf_add_long( keys );
+ macro_buf_add_long( keys, Keymaps[mc] );
return (macro_buf_get());
}
@@ -411,15 +575,31 @@ void macro_add_query( void )
{
unsigned char input;
bool keymap = false;
+ KeymapContext keymc = KC_DEFAULT;
- mpr( "Command (m)acro or (k)eymap? ", MSGCH_PROMPT );
+ mesclr();
+ mpr( "Command (m)acro or keymap [(k) default, (x) level-map or "
+ "(t)argeting]? ", MSGCH_PROMPT );
input = getch();
if (input == 0)
input = getch();
input = tolower( input );
if (input == 'k')
+ {
+ keymap = true;
+ keymc = KC_DEFAULT;
+ }
+ else if (input == 'x')
+ {
keymap = true;
+ keymc = KC_LEVELMAP;
+ }
+ else if (input == 't')
+ {
+ keymap = true;
+ keymc = KC_TARGETING;
+ }
else if (input == 'm')
keymap = false;
else
@@ -429,9 +609,14 @@ void macro_add_query( void )
}
// reference to the appropriate mapping
- macromap &mapref = (keymap ? Keymaps : Macros);
-
- snprintf( info, INFO_SIZE, "Input %s trigger key: ",
+ macromap &mapref = (keymap ? Keymaps[keymc] : Macros);
+
+ snprintf( info, INFO_SIZE, "Input %s%s trigger key: ",
+ keymap?
+ (keymc == KC_DEFAULT? "default " :
+ keymc == KC_LEVELMAP? "level-map " :
+ "targeting ") :
+ "",
(keymap ? "keymap" : "macro") );
mpr( info, MSGCH_PROMPT );
@@ -472,17 +657,19 @@ void macro_add_query( void )
// to resort to editing files and restarting the game. -- bwr
// keyseq act = getch_mul();
- keyseq act;
char buff[4096];
+ get_input_line(buff, sizeof buff);
- get_input_line( buff, sizeof(buff) );
-
- // convert c_str to keyseq
- const int len = strlen( buff );
- for (int i = 0; i < len; i++)
- act.push_back( buff[i] );
+ if (Options.macro_meta_entry)
+ {
+ macro_add( mapref, key, parse_keyseq(buff) );
+ }
+ else
+ {
+ macro_add( mapref, key, buff );
+ }
- macro_add( mapref, key, act );
+ redraw_screen();
}
@@ -495,6 +682,7 @@ int macro_init( void )
std::ifstream f;
keyseq key, action;
bool keymap = false;
+ KeymapContext keymc = KC_DEFAULT;
f.open( get_macro_file().c_str() );
@@ -506,20 +694,38 @@ int macro_init( void )
continue; // skip comments
} else if (s.substr(0, 2) == "K:") {
- key = parse_keyseq(s.substr(2));
+ key = parse_keyseq(s.substr(2));
keymap = true;
-
+ keymc = KC_DEFAULT;
+
+ } else if (s.length() >= 3 && s[0] == 'K' && s[2] == ':') {
+ keymc = KeymapContext( KC_DEFAULT + s[1] - '0' );
+ if (keymc >= KC_DEFAULT && keymc < KC_CONTEXT_COUNT) {
+ key = parse_keyseq(s.substr(3));
+ keymap = true;
+ }
+
} else if (s.substr(0, 2) == "M:") {
- key = parse_keyseq(s.substr(2));
+ key = parse_keyseq(s.substr(2));
keymap = false;
- } else if (s.substr(0, 2) == "A:") {
- action = parse_keyseq(s.substr(2));
- macro_add( (keymap ? Keymaps : Macros), key, action );
- }
+ } else if (s.substr(0, 2) == "A:") {
+ action = parse_keyseq(s.substr(2));
+ macro_add( (keymap ? Keymaps[keymc] : Macros), key, action );
+ }
}
return (0);
}
+#ifdef CLUA_BINDINGS
+void macro_userfn(const char *keys, const char *regname)
+{
+ // TODO: Implement.
+ // Converting 'keys' to a key sequence is the difficulty. Doing it portably
+ // requires a mapping of key names to whatever getch() spits back, unlikely
+ // to happen in a hurry.
+}
+#endif
+
#endif
diff --git a/trunk/source/macro.h b/trunk/source/macro.h
index 9266ae3c26..dba3bafafe 100644
--- a/trunk/source/macro.h
+++ b/trunk/source/macro.h
@@ -21,7 +21,17 @@
#endif
-int getchm(void); // keymaps applied (ie for prompts)
+enum KeymapContext {
+ KC_DEFAULT, // For no-arg getchm().
+ KC_LEVELMAP, // When in the 'X' level map
+ KC_TARGETING, // Only during 'x' and other targeting modes
+
+ KC_CONTEXT_COUNT // Must always be the last
+};
+
+int getchm(int (*rgetch)() = NULL); // keymaps applied (ie for prompts)
+int getchm(KeymapContext context, int (*rgetch)() = NULL);
+
int getch_with_command_macros(void); // keymaps and macros (ie for commands)
void flush_input_buffer( int reason );
@@ -30,11 +40,24 @@ void macro_add_query(void);
int macro_init(void);
void macro_save(void);
+void macro_userfn(const char *keys, const char *registryname);
+
+void macro_buf_add(int key);
+
+bool is_userfunction(int key);
+
+const char *get_userfunction(int key);
+
#endif
#else
#define getch_with_command_macros() getch()
+#define getchm(x) getch()
#define flush_input_buffer(XXX) ;
+#define macro_buf_add(x)
+#define is_userfunction(x) false
+#define get_userfunction(x) NULL
+#define call_userfunction(x)
#endif
diff --git a/trunk/source/makefile.lnx b/trunk/source/makefile.lnx
index 413e8b559f..8b8b8994fd 100644
--- a/trunk/source/makefile.lnx
+++ b/trunk/source/makefile.lnx
@@ -17,12 +17,14 @@ DELETE = rm -f
COPY = cp
OS_TYPE = LINUX
-CFLAGS = -Wall -Wwrite-strings -Wstrict-prototypes \
- -Wmissing-prototypes -Wmissing-declarations \
+CFLAGS = -Wall -Wwrite-strings -fsigned-char \
-g -D$(OS_TYPE) $(EXTRA_FLAGS)
MCHMOD = 2755
-INSTALLDIR = /opt/crawl/bin
+
+# [dshaligram] More common location than /opt/crawl/bin - this is from the
+# Debian patch.
+INSTALLDIR = /usr/games
LIB = -lncurses
# Include for Linux
diff --git a/trunk/source/makefile.mgw b/trunk/source/makefile.mgw
new file mode 100755
index 0000000000..499d3e22d2
--- /dev/null
+++ b/trunk/source/makefile.mgw
@@ -0,0 +1,51 @@
+# Make file for Dungeon Crawl (dos)
+
+# this file contains a list of the libraries.
+# it will make a variable called OBJECTS that contains all the libraries
+include makefile.obj
+
+# need .exe so make will find the right file
+APPNAME = crawl.exe
+CXX = g++
+DELETE = rm
+COPY = copy
+OS_TYPE = WIN32CONSOLE
+CFLAGS = -Wall -Wwrite-strings -Wstrict-prototypes \
+ -Wmissing-prototypes -Wmissing-declarations \
+ -D$(OS_TYPE) $(EXTRA_FLAGS)
+LDFLAGS =
+INSTALLDIR = .
+#LIB = -lcurso -lpano
+LIB = -lwinmm -static -lpcre -lluacore -lluastd
+
+all: $(APPNAME)
+
+install: $(APPNAME)
+ $(COPY) $(APPNAME) ${INSTALLDIR}
+
+clean:
+ $(DELETE) *.o
+
+distclean:
+ $(DELETE) *.o
+ $(DELETE) bones.*
+ $(DELETE) morgue.txt
+ $(DELETE) scores
+ $(DELETE) $(APPNAME)
+ $(DELETE) *.sav
+ $(DELETE) core
+ $(DELETE) *.0*
+ $(DELETE) *.lab
+
+$(APPNAME): $(OBJECTS) libw32c.o
+ ${CXX} ${LDFLAGS} $(INCLUDES) $(CFLAGS) $(OBJECTS) libw32c.o -o $(APPNAME) $(LIB)
+ strip $(APPNAME)
+
+debug: $(OBJECTS)
+ ${CXX} ${LDFLAGS} $(INCLUDES) $(CFLAGS) $(OBJECTS) -o $(APPNAME) $(LIB)
+
+profile: $(OBJECTS)
+ ${CXX} -g -p ${LDFLAGS} $(INCLUDES) $(CFLAGS) $(OBJECTS) -o $(APPNAME) $(LIB)
+
+.cc.o:
+ ${CXX} ${CFLAGS} -c $< ${INCLUDE}
diff --git a/trunk/source/makefile.obj b/trunk/source/makefile.obj
index ce55496c5c..e15334a033 100644
--- a/trunk/source/makefile.obj
+++ b/trunk/source/makefile.obj
@@ -29,6 +29,7 @@ lev-pand.o \
libutil.o \
macro.o \
maps.o \
+menu.o \
message.o \
misc.o \
monplace.o \
@@ -55,8 +56,13 @@ spells4.o \
spl-book.o \
spl-cast.o \
spl-util.o \
+stash.o \
stuff.o \
tags.o \
transfor.o \
+travel.o \
view.o \
-wpn-misc.o
+wpn-misc.o \
+Kills.o \
+mt19937ar.o \
+clua.o
diff --git a/trunk/source/menu.cc b/trunk/source/menu.cc
new file mode 100644
index 0000000000..0728980058
--- /dev/null
+++ b/trunk/source/menu.cc
@@ -0,0 +1,501 @@
+#include <cctype>
+#include "menu.h"
+#include "macro.h"
+#include "view.h"
+
+Menu::Menu( int _flags )
+ : selitem_text(NULL),
+ title(NULL),
+ flags(_flags),
+ first_entry(0),
+ y_offset(0),
+ pagesize(0),
+ items(),
+ sel(NULL),
+ select_filter(),
+ highlighter(new MenuHighlighter),
+ num(-1),
+ lastch(0),
+ alive(false)
+{
+}
+
+Menu::~Menu()
+{
+ for (int i = 0, count = items.size(); i < count; ++i)
+ delete items[i];
+ delete title;
+ delete highlighter;
+}
+
+void Menu::set_highlighter( MenuHighlighter *mh )
+{
+ if (highlighter != mh)
+ delete highlighter;
+ highlighter = mh;
+}
+
+void Menu::set_title( MenuEntry *e )
+{
+ if (title != e)
+ delete title;
+
+ title = e;
+ title->level = MEL_TITLE;
+}
+
+void Menu::add_entry( MenuEntry *entry )
+{
+ items.push_back( entry );
+}
+
+void Menu::reset()
+{
+ first_entry = 0;
+}
+
+std::vector<MenuEntry *> Menu::show()
+{
+ std::vector< MenuEntry * > selected;
+
+ deselect_all(false);
+
+ // Lose lines for the title + room for more.
+ pagesize = get_number_of_lines() - 2;
+
+#ifdef DOS_TERM
+ char buffer[4600];
+
+ gettext(1, 1, 80, 25, buffer);
+ window(1, 1, 80, 25);
+#endif
+
+ do_menu( &selected );
+
+#ifdef DOS_TERM
+ puttext(1, 1, 80, 25, buffer);
+#endif
+
+ return selected;
+}
+
+void Menu::do_menu( std::vector<MenuEntry*> *selected )
+{
+ sel = selected;
+ draw_menu( selected );
+
+ alive = true;
+ while (alive)
+ {
+ int keyin = getchm(c_getch);
+
+ if (!process_key( keyin ))
+ return;
+ }
+}
+
+bool Menu::is_set(int flag) const
+{
+ if (flag == MF_EASY_EXIT && Options.easy_exit_menu)
+ return true;
+ return (flags & flag) == flag;
+}
+
+bool Menu::process_key( int keyin )
+{
+ if (items.size() == 0)
+ return false;
+
+ bool nav = false, repaint = false;
+ switch (keyin)
+ {
+ case CK_ENTER:
+ return false;
+ case CK_ESCAPE:
+ sel->clear();
+ lastch = keyin;
+ return false;
+ case ' ': case CK_PGDN: case '>': case '\'':
+ nav = true;
+ repaint = page_down();
+ if (!repaint && flags && !is_set(MF_EASY_EXIT))
+ {
+ repaint = first_entry != 0;
+ first_entry = 0;
+ }
+ break;
+ case CK_PGUP: case '<': case ';':
+ nav = true;
+ repaint = page_up();
+ break;
+ case CK_UP:
+ nav = true;
+ repaint = line_up();
+ break;
+ case CK_DOWN:
+ nav = true;
+ repaint = line_down();
+ break;
+ default:
+ lastch = keyin;
+
+ // If no selection at all is allowed, exit now.
+ if (!(flags & (MF_SINGLESELECT | MF_MULTISELECT)))
+ return false;
+
+ if (!is_set(MF_NO_SELECT_QTY) && isdigit( keyin ))
+ {
+ if (num > 999)
+ num = -1;
+ num = (num == -1)? keyin - '0' :
+ num * 10 + keyin - '0';
+ }
+
+ select_items( keyin, num );
+ get_selected( sel );
+ if (sel->size() == 1 && (flags & MF_SINGLESELECT))
+ return false;
+ draw_select_count( sel->size() );
+
+ if (flags & MF_ANYPRINTABLE && !isdigit( keyin ))
+ return false;
+
+ break;
+ }
+
+ if (!isdigit( keyin ))
+ num = -1;
+
+ if (nav)
+ {
+ if (repaint)
+ draw_menu( sel );
+ // Easy exit should not kill the menu if there are selected items.
+ else if (sel->empty() && (!flags || is_set(MF_EASY_EXIT)))
+ {
+ sel->clear();
+ return false;
+ }
+ }
+ return true;
+}
+
+bool Menu::draw_title_suffix( const std::string &s, bool titlefirst )
+{
+ int oldx = wherex(), oldy = wherey();
+
+ if (titlefirst)
+ draw_title();
+
+ int x = wherex();
+ if (x > GXM || x < 1)
+ {
+ gotoxy(oldx, oldy);
+ return false;
+ }
+
+ // Note: 1 <= x <= GXM; we have no fear of overflow.
+ unsigned avail_width = GXM - x + 1;
+ std::string towrite = s.length() > avail_width? s.substr(0, avail_width) :
+ s.length() == avail_width? s :
+ s + std::string(avail_width - s.length(), ' ');
+
+ cprintf(towrite.c_str());
+
+ gotoxy( oldx, oldy );
+ return true;
+}
+
+void Menu::draw_select_count( int count )
+{
+ if (!is_set(MF_MULTISELECT))
+ return;
+
+ if (selitem_text)
+ {
+ draw_title_suffix( selitem_text( sel ) );
+ }
+ else
+ {
+ char buf[100] = "";
+ if (count)
+ snprintf(buf, sizeof buf, " (%d item%s) ", count,
+ (count > 1? "s" : ""));
+
+ draw_title_suffix( buf );
+ }
+}
+
+void Menu::get_selected( std::vector<MenuEntry*> *selected ) const
+{
+ selected->clear();
+
+ for (int i = 0, count = items.size(); i < count; ++i)
+ if (items[i]->selected())
+ selected->push_back( items[i] );
+}
+
+void Menu::deselect_all(bool update_view)
+{
+ for (int i = 0, count = items.size(); i < count; ++i)
+ {
+ if (items[i]->level == MEL_ITEM)
+ {
+ items[i]->select(0);
+ if (update_view)
+ draw_item(i);
+ }
+ }
+}
+
+bool Menu::is_hotkey(int i, int key)
+{
+ int end = first_entry + pagesize;
+ if (end > (int) items.size()) end = items.size();
+
+ bool ishotkey = is_set(MF_SINGLESELECT)?
+ items[i]->is_primary_hotkey(key)
+ : items[i]->is_hotkey(key);
+
+ return is_set(MF_SELECT_ANY_PAGE)? ishotkey
+ : ishotkey && i >= first_entry && i < end;
+}
+
+void Menu::select_items( int key, int qty )
+{
+ int x = wherex(), y = wherey();
+
+ if (key == ',' || key == '*')
+ select_index( -1, qty );
+ else if (key == '-')
+ select_index( -1, 0 );
+ else
+ {
+ int final = items.size();
+ bool selected = false;
+
+ // Process all items, in case user hits hotkey for an
+ // item not on the current page.
+
+ // We have to use some hackery to handle items that share
+ // the same hotkey (as for pickup when there's a stack of
+ // >52 items). If there are duplicate hotkeys, the items
+ // are usually separated by at least a page, so we should
+ // only select the item on the current page. This is why we
+ // use two loops, and check to see if we've matched an item
+ // by its primary hotkey (which is assumed to always be
+ // hotkeys[0]), in which case, we stop selecting further
+ // items.
+ for (int i = first_entry; i < final; ++i)
+ {
+ if (is_hotkey( i, key ))
+ {
+ select_index( i, qty );
+ if (items[i]->hotkeys[0] == key)
+ {
+ selected = true;
+ break;
+ }
+ }
+ }
+
+ if (!selected)
+ {
+ for (int i = 0; i < first_entry; ++i)
+ {
+ if (is_hotkey( i, key ))
+ {
+ select_index( i, qty );
+ if (items[i]->hotkeys[0] == key)
+ {
+ selected = true;
+ break;
+ }
+ }
+ }
+ }
+ }
+ gotoxy( x, y );
+}
+
+bool Menu::is_selectable(int item) const
+{
+ if (select_filter.empty()) return true;
+
+ std::string text = items[item]->get_text();
+ for (int i = 0, count = select_filter.size(); i < count; ++i)
+ {
+ if (select_filter[i].matches(text))
+ return true;
+ }
+ return false;
+}
+
+void Menu::select_index( int index, int qty )
+{
+ int si = index == -1? first_entry : index;
+
+ if (index == -1)
+ {
+ if (flags & MF_MULTISELECT)
+ {
+ for (int i = 0, count = items.size(); i < count; ++i)
+ {
+ if (items[i]->level != MEL_ITEM) continue;
+ if (is_hotkey(i, items[i]->hotkeys[0]) && is_selectable(i))
+ {
+ items[i]->select( qty );
+ draw_item( i );
+ }
+ }
+ }
+ }
+ else if (items[si]->level == MEL_SUBTITLE && (flags & MF_MULTISELECT))
+ {
+ for (int i = si + 1, count = items.size(); i < count; ++i)
+ {
+ if (items[i]->level != MEL_ITEM)
+ break;
+ if (is_hotkey(i, items[i]->hotkeys[0]))
+ {
+ items[i]->select( qty );
+ draw_item( i );
+ }
+ }
+ }
+ else if (items[si]->level == MEL_ITEM &&
+ (flags & (MF_SINGLESELECT | MF_MULTISELECT)))
+ {
+ items[si]->select( qty );
+ draw_item( si );
+ }
+}
+
+void Menu::draw_menu( std::vector< MenuEntry* > *selected )
+{
+ clrscr();
+
+ draw_title();
+ draw_select_count( selected->size() );
+ y_offset = 2;
+
+ int end = first_entry + pagesize;
+ if (end > (int) items.size()) end = items.size();
+
+ for (int i = first_entry; i < end; ++i)
+ {
+ draw_item( i );
+ }
+ if (end < (int) items.size())
+ {
+ gotoxy( 1, y_offset + pagesize );
+ textcolor( LIGHTGREY );
+ cprintf("-more-");
+ }
+}
+
+void Menu::update_title()
+{
+ int x = wherex(), y = wherey();
+ draw_title();
+ gotoxy(x, y);
+}
+
+int Menu::item_colour(const MenuEntry *entry) const
+{
+ int icol = -1;
+ if (highlighter)
+ icol = highlighter->entry_colour(entry);
+
+ return (icol == -1? entry->colour : icol);
+}
+
+void Menu::draw_title()
+{
+ if (title)
+ {
+ gotoxy(1, 1);
+ textcolor( item_colour(title) );
+ cprintf( "%s", title->get_text().c_str() );
+ }
+}
+
+void Menu::draw_item( int index ) const
+{
+ if (index < first_entry || index >= first_entry + pagesize)
+ return;
+
+ gotoxy( 1, y_offset + index - first_entry );
+ textcolor( item_colour(items[index]) );
+ cprintf( "%s", items[index]->get_text().c_str() );
+}
+
+bool Menu::page_down()
+{
+ int old_first = first_entry;
+
+ if ((int) items.size() > first_entry + pagesize)
+ {
+ first_entry += pagesize;
+ //if (first_entry + pagesize > (int) items.size())
+ // first_entry = items.size() - pagesize;
+
+ if (old_first != first_entry)
+ return true;
+ }
+ return false;
+}
+
+bool Menu::page_up()
+{
+ int old_first = first_entry;
+
+ if (first_entry > 0)
+ {
+ if ((first_entry -= pagesize) < 0)
+ first_entry = 0;
+ if (old_first != first_entry)
+ return true;
+ }
+ return false;
+}
+
+bool Menu::line_down()
+{
+ if (first_entry + pagesize < (int) items.size())
+ {
+ ++first_entry;
+ return true;
+ }
+ return false;
+}
+
+bool Menu::line_up()
+{
+ if (first_entry > 0)
+ {
+ --first_entry;
+ return true;
+ }
+ return false;
+}
+
+/////////////////////////////////////////////////////////////////
+// Menu colouring
+//
+
+int menu_colour(const std::string &text)
+{
+ for (int i = 0, size = Options.menu_colour_mappings.size(); i < size; ++i)
+ {
+ colour_mapping &cm = Options.menu_colour_mappings[i];
+ if (cm.pattern.matches(text))
+ return (cm.colour);
+ }
+ return (-1);
+}
+
+int MenuHighlighter::entry_colour(const MenuEntry *entry) const
+{
+ return (::menu_colour(entry->get_text()));
+}
diff --git a/trunk/source/menu.h b/trunk/source/menu.h
new file mode 100755
index 0000000000..4f1915fe31
--- /dev/null
+++ b/trunk/source/menu.h
@@ -0,0 +1,216 @@
+#ifndef __MENU_H__
+#define __MENU_H__
+
+#include <string>
+#include <vector>
+#include <algorithm>
+#include "AppHdr.h"
+#include "defines.h"
+#include "libutil.h"
+
+enum MenuEntryLevel
+{
+ MEL_NONE = -1,
+ MEL_TITLE,
+ MEL_SUBTITLE,
+ MEL_ITEM
+};
+
+struct menu_letter
+{
+ char letter;
+
+ menu_letter() : letter('a') { }
+ menu_letter(char c) : letter(c) { }
+
+ operator char () const { return letter; }
+
+ const menu_letter &operator ++ ()
+ {
+ letter = letter == 'z'? 'A' :
+ letter == 'Z'? 'a' :
+ letter + 1;
+ return *this;
+ }
+
+ menu_letter operator ++ (int postfix)
+ {
+ menu_letter copy = *this;
+ this->operator++();
+ return copy;
+ }
+};
+
+struct item_def;
+struct MenuEntry
+{
+ std::string text;
+ int quantity, selected_qty;
+ int colour;
+ std::vector<int> hotkeys;
+ MenuEntryLevel level;
+ void *data;
+
+ MenuEntry( const std::string &txt = std::string(""),
+ MenuEntryLevel lev = MEL_ITEM,
+ int qty = 0,
+ int hotk = 0 ) :
+ text(txt), quantity(qty), selected_qty(0), colour(-1),
+ hotkeys(), level(lev), data(NULL)
+ {
+ colour = lev == MEL_ITEM? LIGHTGREY :
+ lev == MEL_SUBTITLE? BLUE :
+ WHITE;
+ if (hotk)
+ hotkeys.push_back( hotk );
+ }
+ virtual ~MenuEntry() { }
+
+ void add_hotkey( int key )
+ {
+ if (key && !is_hotkey(key))
+ hotkeys.push_back( key );
+ }
+
+ bool is_hotkey( int key ) const
+ {
+ return find( hotkeys.begin(), hotkeys.end(), key ) != hotkeys.end();
+ }
+
+ bool is_primary_hotkey( int key ) const
+ {
+ return hotkeys.size()? hotkeys[0] == key : false;
+ }
+
+ virtual std::string get_text() const
+ {
+ if (level == MEL_ITEM && hotkeys.size())
+ {
+ char buf[300];
+ snprintf(buf, sizeof buf,
+ "%c - %s", hotkeys[0], text.c_str());
+ return std::string(buf);
+ }
+ return std::string(level == MEL_SUBTITLE? " " :
+ level == MEL_ITEM? "" : " ") + text;
+ }
+
+ virtual bool selected() const
+ {
+ return selected_qty > 0 && quantity;
+ }
+
+ void select( int qty = -1 )
+ {
+ if (selected())
+ selected_qty = 0;
+ else if (quantity)
+ selected_qty = qty == -1? quantity : qty;
+ }
+};
+
+class MenuHighlighter
+{
+public:
+ virtual int entry_colour(const MenuEntry *entry) const;
+ virtual ~MenuHighlighter() { }
+};
+
+enum MenuFlag
+{
+ MF_NOSELECT = 0, // No selection is permitted
+ MF_SINGLESELECT = 1, // Select just one item
+ MF_MULTISELECT = 2, // Select multiple items
+ MF_NO_SELECT_QTY = 4, // Disallow partial selections
+ MF_ANYPRINTABLE = 8, // Any printable character is valid, and
+ // closes the menu.
+ MF_SELECT_ANY_PAGE = 16, // Allow selections to occur on any page.
+
+ MF_EASY_EXIT = 64,
+};
+
+///////////////////////////////////////////////////////////////////////
+// NOTE
+// As a general contract, any pointers you pass to Menu methods are OWNED BY
+// THE MENU, and will be deleted by the Menu on destruction. So any pointers
+// you pass in MUST be allocated with new, or Crawl will crash.
+
+#define NUMBUFSIZ 10
+class Menu
+{
+public:
+ Menu( int flags = MF_MULTISELECT );
+ virtual ~Menu();
+
+ void set_flags( int flags ) { this->flags = flags; }
+ int get_flags() const { return flags; }
+ bool is_set( int flag ) const;
+
+ bool draw_title_suffix( const std::string &s, bool titlefirst = true );
+ void update_title();
+
+ void set_highlighter( MenuHighlighter *h );
+ void set_title( MenuEntry *e );
+ void add_entry( MenuEntry *entry );
+ void get_selected( std::vector<MenuEntry*> *sel ) const;
+
+ void set_select_filter( std::vector<text_pattern> filter )
+ {
+ select_filter = filter;
+ }
+
+ unsigned char getkey() const { return lastch; }
+
+ void reset();
+ std::vector<MenuEntry *> show();
+
+public:
+ typedef std::string (*selitem_tfn)( const std::vector<MenuEntry*> *sel );
+
+ selitem_tfn selitem_text;
+
+protected:
+ MenuEntry *title;
+ int flags;
+
+ int first_entry, y_offset;
+ int pagesize;
+
+ std::vector<MenuEntry*> items;
+ std::vector<MenuEntry*> *sel;
+ std::vector<text_pattern> select_filter;
+
+ // Class that is queried to colour menu entries.
+ MenuHighlighter *highlighter;
+
+ int num;
+
+ unsigned char lastch;
+
+ bool alive;
+
+ void do_menu( std::vector<MenuEntry*> *selected );
+ virtual void draw_select_count( int count );
+ void draw_item( int index ) const;
+ virtual void draw_title();
+ void draw_menu( std::vector<MenuEntry*> *selected );
+ bool page_down();
+ bool line_down();
+ bool page_up();
+ bool line_up();
+
+ void deselect_all(bool update_view = true);
+ void select_items( int key, int qty = -1 );
+ void select_index( int index, int qty = -1 );
+
+ bool is_hotkey(int index, int key );
+ bool is_selectable(int index) const;
+
+ int item_colour(const MenuEntry *me) const;
+
+ virtual bool process_key( int keyin );
+};
+
+int menu_colour(const std::string &itemtext);
+
+#endif
diff --git a/trunk/source/message.cc b/trunk/source/message.cc
index 4139d465ab..65faacbbd8 100644
--- a/trunk/source/message.cc
+++ b/trunk/source/message.cc
@@ -24,8 +24,11 @@
#include "externs.h"
+#include "initfile.h"
#include "macro.h"
+#include "player.h"
#include "stuff.h"
+#include "travel.h"
#include "view.h"
@@ -171,7 +174,7 @@ static char channel_to_colour( int channel, int param )
case MSGCH_ROTTEN_MEAT:
case MSGCH_EQUIPMENT:
default:
- ret = LIGHTGREY;
+ ret = param > 0? param : LIGHTGREY;
break;
}
break;
@@ -220,7 +223,38 @@ void mpr(const char *inf, int channel, int param)
if (colour == MSGCOL_MUTED)
return;
- you.running = 0;
+ interrupt_activity( AI_MESSAGE, channel_to_str(channel) + ":" + inf );
+
+ // If you're travelling, only certain user-specified messages can break
+ // travel
+ if (you.running < 0)
+ {
+ std::string message = inf;
+ for (unsigned i = 0; i < Options.stop_travel.size(); ++i)
+ {
+ if (Options.stop_travel[i].is_filtered( channel, message ))
+ {
+ stop_running();
+ break;
+ }
+ }
+ }
+
+ if (Options.sound_mappings.size() > 0)
+ {
+ std::string message = inf;
+ for (unsigned i = 0; i < Options.sound_mappings.size(); i++)
+ {
+ // Maybe we should allow message channel matching as for
+ // stop_travel?
+ if (Options.sound_mappings[i].pattern.matches(message))
+ {
+ play_sound(Options.sound_mappings[i].soundfile.c_str());
+ break;
+ }
+ }
+ }
+
flush_input_buffer( FLUSH_ON_MESSAGE );
#ifdef DOS_TERM
@@ -305,9 +339,14 @@ void mesclr( bool force )
#else
int numLines = get_number_of_lines() - startLine + 1;
+
+ char blankline[81];
+ memset(blankline, ' ', sizeof blankline);
+ blankline[80] = 0;
+
for (int i = 0; i < numLines; i++)
{
- cprintf( " " );
+ cprintf( blankline );
if (i < numLines - 1)
{
@@ -352,6 +391,37 @@ void more(void)
mesclr( (Message_Line >= get_number_of_lines() - 18) );
} // end more()
+std::string get_last_messages(int mcount)
+{
+ if (mcount <= 0) return std::string();
+ if (mcount > NUM_STORED_MESSAGES) mcount = NUM_STORED_MESSAGES;
+
+ bool full_buffer = Store_Message[ NUM_STORED_MESSAGES - 1 ].text.length() == 0;
+ int initial = Next_Message - mcount;
+ if (initial < 0 || initial > NUM_STORED_MESSAGES)
+ initial = full_buffer? initial + NUM_STORED_MESSAGES : 0;
+
+ std::string text;
+ int count = 0;
+ for (int i = initial; i != Next_Message; )
+ {
+ if (Store_Message[i].text.length())
+ {
+ text += Store_Message[i].text;
+ text += EOL;
+ count++;
+ }
+
+ if (++i >= NUM_STORED_MESSAGES)
+ i -= NUM_STORED_MESSAGES;
+ }
+
+ // An extra line of clearance.
+ if (count) text += EOL;
+
+ return text;
+}
+
void replay_messages(void)
{
int win_start_line = 0;
diff --git a/trunk/source/message.h b/trunk/source/message.h
index c5fdc5a312..c7fe736ef1 100644
--- a/trunk/source/message.h
+++ b/trunk/source/message.h
@@ -74,4 +74,10 @@ void set_colour(char set_message_colour);
* *********************************************************************** */
bool any_messages(void);
+// last updated 13oct2003 {dlb}
+/* ***********************************************************************
+ * called from: chardump
+ * *********************************************************************** */
+std::string get_last_messages(int mcount);
+
#endif
diff --git a/trunk/source/misc.cc b/trunk/source/misc.cc
index 85ebb311b7..705a8acadb 100644
--- a/trunk/source/misc.cc
+++ b/trunk/source/misc.cc
@@ -18,6 +18,11 @@
#if !(defined(__IBMCPP__) || defined(__BCPLUSPLUS__))
#include <unistd.h>
#endif
+
+#ifdef __MINGW32__
+#include <io.h>
+#endif
+
#include <stdlib.h>
#include <stdio.h>
@@ -49,6 +54,7 @@
#include "spl-cast.h"
#include "stuff.h"
#include "transfor.h"
+#include "travel.h"
#include "view.h"
@@ -298,7 +304,7 @@ void in_a_cloud(void)
return;
}
- if (!player_equip( EQ_BODY_ARMOUR, ARM_STEAM_DRAGON_ARMOUR ))
+ if (player_equip( EQ_BODY_ARMOUR, ARM_STEAM_DRAGON_ARMOUR ))
{
mpr("It doesn't seem to affect you.");
return;
@@ -415,13 +421,24 @@ void up_stairs(void)
}
if (you.your_level == 0
- && !yesno("Are you sure you want to leave the Dungeon?", false))
+ && !yesno("Are you sure you want to leave the Dungeon?", false, 'n'))
{
mpr("Alright, then stay!");
return;
}
- unsigned char old_level = you.your_level;
+ unsigned char old_level = you.your_level;
+
+ // Interlevel travel data:
+ bool collect_travel_data = you.level_type != LEVEL_LABYRINTH
+ && you.level_type != LEVEL_ABYSS
+ && you.level_type != LEVEL_PANDEMONIUM;
+
+ level_id old_level_id = level_id::get_current_level_id();
+ LevelInfo &old_level_info = travel_cache.get_level_info(old_level_id);
+ int stair_x = you.x_pos, stair_y = you.y_pos;
+ if (collect_travel_data)
+ old_level_info.update();
// Make sure we return to our main dungeon level... labyrinth entrances
// in the abyss or pandemonium a bit trouble (well the labyrinth does
@@ -524,12 +541,70 @@ void up_stairs(void)
viewwindow(1, true);
-
if (you.skills[SK_TRANSLOCATIONS] > 0 && !allow_control_teleport( true ))
mpr( "You sense a powerful magical force warping space.", MSGCH_WARN );
+
+ // Tell the travel code that we're now on a new level
+ travel_init_new_level();
+ if (collect_travel_data)
+ {
+ // Update stair information for the stairs we just ascended, and the
+ // down stairs we're currently on.
+ level_id new_level_id = level_id::get_current_level_id();
+
+ if (you.level_type != LEVEL_PANDEMONIUM &&
+ you.level_type != LEVEL_ABYSS &&
+ you.level_type != LEVEL_LABYRINTH)
+ {
+ LevelInfo &new_level_info =
+ travel_cache.get_level_info(new_level_id);
+ new_level_info.update();
+
+ // First we update the old level's stair.
+ level_pos lp;
+ lp.id = new_level_id;
+ lp.pos.x = you.x_pos;
+ lp.pos.y = you.y_pos;
+
+ bool guess = false;
+ // Ugly hack warning:
+ // The stairs in the Vestibule of Hell exhibit special behaviour:
+ // they always lead back to the dungeon level that the player
+ // entered the Vestibule from. This means that we need to pretend
+ // we don't know where the upstairs from the Vestibule go each time
+ // we take it. If we don't, interlevel travel may try to use portals
+ // to Hell as shortcuts between dungeon levels, which won't work,
+ // and will confuse the dickens out of the player (well, it confused
+ // the dickens out of me when it happened).
+ if (new_level_id.branch == BRANCH_MAIN_DUNGEON &&
+ old_level_id.branch == BRANCH_VESTIBULE_OF_HELL)
+ {
+ lp.id.depth = -1;
+ lp.pos.x = lp.pos.y = -1;
+ guess = true;
+ }
+
+ old_level_info.update_stair(stair_x, stair_y, lp, guess);
+
+ // We *guess* that going up a staircase lands us on a downstair,
+ // and that we can descend that downstair and get back to where we
+ // came from. This assumption is guaranteed false when climbing out
+ // of one of the branches of Hell.
+ if (new_level_id.branch != BRANCH_VESTIBULE_OF_HELL)
+ {
+ // Set the new level's stair, assuming arbitrarily that going
+ // downstairs will land you on the same upstairs you took to
+ // begin with (not necessarily true).
+ lp.id = old_level_id;
+ lp.pos.x = stair_x;
+ lp.pos.y = stair_y;
+ new_level_info.update_stair(you.x_pos, you.y_pos, lp, true);
+ }
+ }
+ }
} // end up_stairs()
-void down_stairs( bool remove_stairs, int old_level )
+void down_stairs( bool remove_stairs, int old_level, bool force )
{
int i;
char old_level_type = you.level_type;
@@ -577,7 +652,8 @@ void down_stairs( bool remove_stairs, int old_level )
return;
}
- if (player_is_levitating() && !wearing_amulet(AMU_CONTROLLED_FLIGHT))
+ if (!force && player_is_levitating()
+ && !wearing_amulet(AMU_CONTROLLED_FLIGHT))
{
mpr("You're floating high up above the floor!");
return;
@@ -616,6 +692,18 @@ void down_stairs( bool remove_stairs, int old_level )
}
}
+ // Interlevel travel data:
+ bool collect_travel_data = you.level_type != LEVEL_LABYRINTH
+ && you.level_type != LEVEL_ABYSS
+ && you.level_type != LEVEL_PANDEMONIUM;
+
+ level_id old_level_id = level_id::get_current_level_id();
+ LevelInfo &old_level_info = travel_cache.get_level_info(old_level_id);
+ int stair_x = you.x_pos, stair_y = you.y_pos;
+ if (collect_travel_data)
+ old_level_info.update();
+
+
if (you.level_type == LEVEL_PANDEMONIUM
&& stair_find == DNGN_TRANSIT_PANDEMONIUM)
{
@@ -641,7 +729,9 @@ void down_stairs( bool remove_stairs, int old_level )
mpr("Welcome to Hell!");
mpr("Please enjoy your stay.");
- more();
+ // Kill -more- prompt if we're traveling.
+ if (!you.running)
+ more();
you.your_level = 26; // = 59;
}
@@ -913,6 +1003,39 @@ void down_stairs( bool remove_stairs, int old_level )
if (you.skills[SK_TRANSLOCATIONS] > 0 && !allow_control_teleport( true ))
mpr( "You sense a powerful magical force warping space.", MSGCH_WARN );
+
+ travel_init_new_level();
+ if (collect_travel_data)
+ {
+ // Update stair information for the stairs we just descended, and the
+ // upstairs we're currently on.
+ level_id new_level_id = level_id::get_current_level_id();
+
+ if (you.level_type != LEVEL_PANDEMONIUM &&
+ you.level_type != LEVEL_ABYSS &&
+ you.level_type != LEVEL_LABYRINTH)
+ {
+ LevelInfo &new_level_info =
+ travel_cache.get_level_info(new_level_id);
+ new_level_info.update();
+
+ // First we update the old level's stair.
+ level_pos lp;
+ lp.id = new_level_id;
+ lp.pos.x = you.x_pos;
+ lp.pos.y = you.y_pos;
+
+ old_level_info.update_stair(stair_x, stair_y, lp);
+
+ // Then the new level's stair, assuming arbitrarily that going
+ // upstairs will land you on the same downstairs you took to begin
+ // with (not necessarily true).
+ lp.id = old_level_id;
+ lp.pos.x = stair_x;
+ lp.pos.y = stair_y;
+ new_level_info.update_stair(you.x_pos, you.y_pos, lp, true);
+ }
+ }
} // end down_stairs()
void new_level(void)
diff --git a/trunk/source/misc.h b/trunk/source/misc.h
index 6ff186a84d..7a754ffdd0 100644
--- a/trunk/source/misc.h
+++ b/trunk/source/misc.h
@@ -47,7 +47,7 @@ void disarm_trap(struct dist &disa);
/* ***********************************************************************
* called from: acr - effects - spells3
* *********************************************************************** */
-void down_stairs(bool remove_stairs, int old_level);
+void down_stairs(bool remove_stairs, int old_level, bool force = false);
// last updated 12may2000 {dlb}
diff --git a/trunk/source/mon-util.cc b/trunk/source/mon-util.cc
index a94133f18e..4df429aab7 100644
--- a/trunk/source/mon-util.cc
+++ b/trunk/source/mon-util.cc
@@ -1297,6 +1297,27 @@ bool mons_friendly(struct monsters *m)
return (m->attitude == ATT_FRIENDLY || mons_has_ench(m, ENCH_CHARM));
}
+bool mons_is_stabbable(struct monsters *m)
+{
+ // Make sure oklob plants are never highlighted. That'll defeat the
+ // point of making them look like normal plants.
+ return (!mons_flag(m->type, M_NO_EXP_GAIN)
+ && m->type != MONS_OKLOB_PLANT
+ && !mons_friendly(m)
+ && m->behaviour == BEH_SLEEP);
+}
+
+bool mons_maybe_stabbable(struct monsters *m)
+{
+ return (!mons_flag(m->type, M_NO_EXP_GAIN)
+ && m->type != MONS_OKLOB_PLANT
+ && !mons_friendly(m)
+ && ((m->foe != MHITYOU && !testbits(m->flags, MF_BATTY))
+ || (mons_has_ench(m, ENCH_CONFUSION) &&
+ !mons_flag(m->type, M_CONFUSED))
+ || m->behaviour == BEH_FLEE));
+}
+
/* ******************************************************************
// In the name of England, I declare this function wasteful! {dlb}
diff --git a/trunk/source/mon-util.h b/trunk/source/mon-util.h
index d9a2cb9bdb..9a1fc22688 100644
--- a/trunk/source/mon-util.h
+++ b/trunk/source/mon-util.h
@@ -23,8 +23,10 @@
#if defined(macintosh) || defined(__IBMCPP__) || defined(SOLARIS) || defined(__BCPLUSPLUS__)
#define PACKED
#else
+#ifndef PACKED
#define PACKED __attribute__ ((packed))
#endif
+#endif
// leaves no skeleton? ("blob" monsters?)
// if weight=0 or zombie_size=0, this is always true
@@ -100,29 +102,31 @@
#define I_REPTILE 6
+// [dshaligram] PACKED is *not* relevant; the amount of padding space it
+// saves is not enough to merit it.
struct monsterentry
{
- short mc PACKED; // monster number
+ short mc; // monster number
- unsigned char showchar PACKED, colour PACKED;
- const char *name /*[32]*/PACKED; //longest is 23 till now (31 is max alowed here)
+ unsigned char showchar, colour;
+ const char *name /*[32]*/; //longest is 23 till now (31 is max alowed here)
- int bitfields PACKED;
- short weight PACKED;
+ int bitfields;
+ short weight;
// experience is calculated like this:
// ((((max_hp / 7) + 1) * (mHD * mHD) + 1) * exp_mod) / 10
// ^^^^^^ see below at hpdice
// Note that this may make draining attacks less attractive (LRH)
- char exp_mod PACKED;
+ char exp_mod;
- unsigned short charclass PACKED; //
+ unsigned short charclass; //
- char holiness PACKED; // -1=holy,0=normal,1=undead,2=very very evil
+ char holiness; // -1=holy,0=normal,1=undead,2=very very evil
- short resist_magic PACKED; // (positive is ??)
+ short resist_magic; // (positive is ??)
// max damage in a turn is total of these four?
- unsigned char damage[4] PACKED;
+ unsigned char damage[4];
// hpdice[4]: [0]=HD [1]=min_hp [2]=rand_hp [3]=add_hp
// min hp = [0]*[1]+[3] & max hp = [0]*([1]+[2])+[3])
@@ -131,26 +135,26 @@ struct monsterentry
// 105 < hp < 165
// hp will be around 135 each time. (assuming an good random number generator)
// !!!!!!! The system is exactly the same as before, only the way of writing changed !!!!
- unsigned char hpdice[4] PACKED; // until we have monsters with 32767 hp,this is easily possible
+ unsigned char hpdice[4]; // until we have monsters with 32767 hp,this is easily possible
- char AC PACKED; // armour class
+ char AC; // armour class
- char ev PACKED; // evasion
+ char ev; // evasion
- char speed PACKED, speed_inc PACKED; // duh!
+ char speed, speed_inc; // duh!
- short sec PACKED; // not used (250) most o/t time
+ short sec; // not used (250) most o/t time
// eating the corpse: 1=clean,2=might be contaminated,3=poison,4=very bad
- char corpse_thingy PACKED;
+ char corpse_thingy;
// 0=no zombie, 1=small zombie (z) 107, 2=_BIG_ zombie (Z) 108
- char zombie_size PACKED;
+ char zombie_size;
// 0-12: see above, -1=random one of (0-7)
- char shouts PACKED;
+ char shouts;
// AI things?
- char intel PACKED; // 0=none, 1=worst...4=best
+ char intel; // 0=none, 1=worst...4=best
- char gmon_use PACKED;
+ char gmon_use;
}; // mondata[] - again, no idea why this was externed {dlb}
@@ -456,6 +460,9 @@ int mons_del_ench( struct monsters *mon, unsigned int ench,
bool mons_add_ench( struct monsters *mon, unsigned int ench );
+bool mons_is_stabbable(struct monsters *m);
+
+bool mons_maybe_stabbable(struct monsters *m);
bool check_mons_resist_magic( struct monsters *monster, int pow );
diff --git a/trunk/source/monplace.cc b/trunk/source/monplace.cc
index 6c9505cd47..d3112debc9 100644
--- a/trunk/source/monplace.cc
+++ b/trunk/source/monplace.cc
@@ -36,11 +36,12 @@
static int band_member(int band, int power);
static int choose_band( int mon_type, int power, int &band_size );
static int place_monster_aux(int mon_type, char behaviour, int target,
- int px, int py, int power, int extra, bool first_band_member);
+ int px, int py, int power, int extra, bool first_band_member,
+ int dur = 0);
bool place_monster(int &id, int mon_type, int power, char behaviour,
int target, bool summoned, int px, int py, bool allow_bands,
- int proximity, int extra)
+ int proximity, int extra, int dur)
{
int band_size = 0;
int band_monsters[BIG_BAND]; // band monster types
@@ -334,7 +335,7 @@ bool place_monster(int &id, int mon_type, int power, char behaviour,
for(i = 1; i < band_size; i++)
{
place_monster_aux( band_monsters[i], behaviour, target, px, py,
- lev_mons, extra, false );
+ lev_mons, extra, false, dur );
}
// placement of first monster, at least, was a success.
@@ -343,7 +344,7 @@ bool place_monster(int &id, int mon_type, int power, char behaviour,
static int place_monster_aux( int mon_type, char behaviour, int target,
int px, int py, int power, int extra,
- bool first_band_member )
+ bool first_band_member, int dur )
{
int id, i;
char grid_wanted;
@@ -531,6 +532,10 @@ static int place_monster_aux( int mon_type, char behaviour, int target,
menv[id].behaviour = BEH_WANDER;
}
+ // dur should always be ENCH_ABJ_xx
+ if (dur >= ENCH_ABJ_I && dur <= ENCH_ABJ_VI)
+ mons_add_ench(&menv[id], dur );
+
menv[id].foe = target;
return (id);
@@ -1005,7 +1010,8 @@ static int band_member(int band, int power)
// PUBLIC FUNCTION -- mons_place().
int mons_place( int mon_type, char behaviour, int target, bool summoned,
- int px, int py, int level_type, int proximity, int extra )
+ int px, int py, int level_type, int proximity, int extra,
+ int dur )
{
int mon_count = 0;
int temp_rand; // probabilty determination {dlb}
@@ -1068,7 +1074,7 @@ int mons_place( int mon_type, char behaviour, int target, bool summoned,
}
if (place_monster( mid, mon_type, power, behaviour, target, summoned,
- px, py, permit_bands, proximity, extra ) == false)
+ px, py, permit_bands, proximity, extra, dur ) == false)
{
return (-1);
}
@@ -1120,7 +1126,7 @@ int create_monster( int cls, int dur, int beha, int cr_x, int cr_y,
if (empty_surrounds( cr_x, cr_y, spcw, true, empty ))
{
summd = mons_place( cls, beha, hitting, true, empty[0], empty[1],
- you.level_type, 0, zsec );
+ you.level_type, 0, zsec, dur );
}
// determine whether creating a monster is successful (summd != -1) {dlb}:
diff --git a/trunk/source/monplace.h b/trunk/source/monplace.h
index fc1595ee1e..f2fde75111 100644
--- a/trunk/source/monplace.h
+++ b/trunk/source/monplace.h
@@ -36,7 +36,8 @@
* *********************************************************************** */
int mons_place( int mon_type, char behaviour, int target, bool summoned,
int px, int py, int level_type = LEVEL_DUNGEON,
- int proximity = PROX_ANYWHERE, int extra = 250 );
+ int proximity = PROX_ANYWHERE, int extra = 250,
+ int dur = 0 );
// last updated 12may2000 {dlb}
/* ***********************************************************************
@@ -74,6 +75,7 @@ int summon_any_demon( char demon_class );
* *********************************************************************** */
bool place_monster( int &id, int mon_type, int power, char behaviour,
int target, bool summoned, int px, int py, bool allow_bands,
- int proximity = PROX_ANYWHERE, int extra = 250 );
+ int proximity = PROX_ANYWHERE, int extra = 250,
+ int dur = 0 );
#endif // MONPLACE_H
diff --git a/trunk/source/monstuff.cc b/trunk/source/monstuff.cc
index f2af9744a4..644462b798 100644
--- a/trunk/source/monstuff.cc
+++ b/trunk/source/monstuff.cc
@@ -230,7 +230,8 @@ bool curse_an_item( char which, char power )
return (true);
}
-static void monster_drop_ething(struct monsters *monster)
+static void monster_drop_ething(struct monsters *monster,
+ bool mark_item_origins = false)
{
/* drop weapons & missiles last (ie on top) so others pick up */
int i; // loop variable {dlb}
@@ -257,6 +258,10 @@ static void monster_drop_ething(struct monsters *monster)
else
{
move_item_to_grid( &item, monster->x, monster->y );
+ if (mark_item_origins && is_valid_item(mitm[item]))
+ {
+ origin_set_monster(mitm[item], monster);
+ }
}
monster->inv[i] = NON_ITEM;
@@ -405,6 +410,10 @@ void monster_die(struct monsters *monster, char killer, int i)
{
case KILL_YOU: /* You kill in combat. */
case KILL_YOU_MISSILE: /* You kill by missile or beam. */
+ {
+ bool created_friendly =
+ testbits(monster->flags, MF_CREATED_FRIENDLY);
+
strcpy(info, "You ");
strcat(info, (wounded_damaged(monster->type)) ? "destroy" : "kill");
strcat(info, " ");
@@ -414,7 +423,7 @@ void monster_die(struct monsters *monster, char killer, int i)
if (death_message)
mpr(info, MSGCH_MONSTER_DAMAGE, MDAM_DEAD);
- if (!testbits(monster->flags, MF_CREATED_FRIENDLY))
+ if (!created_friendly)
{
gain_exp(exper_value( monster ));
}
@@ -434,7 +443,7 @@ void monster_die(struct monsters *monster, char killer, int i)
// Trying to prevent summoning abuse here, so we're trying to
// prevent summoned creatures from being being done_good kills,
// Only affects monsters friendly when created.
- if (!testbits(monster->flags, MF_CREATED_FRIENDLY))
+ if (!created_friendly)
{
if (you.duration[DUR_PRAYER])
{
@@ -464,9 +473,12 @@ void monster_die(struct monsters *monster, char killer, int i)
}
}
+ // Divine health and mp restoration doesn't happen when killing
+ // born-friendly monsters. The mutation still applies, however.
if (you.mutation[MUT_DEATH_STRENGTH]
- || (you.religion == GOD_MAKHLEB && you.duration[DUR_PRAYER]
- && (!player_under_penance() && random2(you.piety) >= 30)))
+ || (!created_friendly &&
+ you.religion == GOD_MAKHLEB && you.duration[DUR_PRAYER] &&
+ (!player_under_penance() && random2(you.piety) >= 30)))
{
if (you.hp < you.hp_max)
{
@@ -476,7 +488,8 @@ void monster_die(struct monsters *monster, char killer, int i)
}
}
- if ((you.religion == GOD_MAKHLEB || you.religion == GOD_VEHUMET)
+ if (!created_friendly
+ && (you.religion == GOD_MAKHLEB || you.religion == GOD_VEHUMET)
&& you.duration[DUR_PRAYER]
&& (!player_under_penance() && random2(you.piety) >= 30))
{
@@ -500,6 +513,7 @@ void monster_die(struct monsters *monster, char killer, int i)
}
}
break;
+ }
case KILL_MON: /* Monster kills in combat */
case KILL_MON_MISSILE: /* Monster kills by missile or beam */
@@ -624,6 +638,8 @@ void monster_die(struct monsters *monster, char killer, int i)
if (killer != KILL_RESET)
{
+ you.kills.record_kill(monster, killer, pet_kill);
+
if (mons_has_ench(monster, ENCH_ABJ_I, ENCH_ABJ_VI))
{
if (mons_weight(mons_charclass(monster->type)))
@@ -653,7 +669,10 @@ void monster_die(struct monsters *monster, char killer, int i)
}
}
- monster_drop_ething(monster);
+ monster_drop_ething(monster,
+ killer == KILL_YOU_MISSILE
+ || killer == KILL_YOU
+ || pet_kill);
monster_cleanup(monster);
} // end monster_die
@@ -856,6 +875,7 @@ bool monster_polymorph( struct monsters *monster, int targetc, int power )
char str_polymon[INFO_SIZE] = ""; // cannot use info[] here {dlb}
bool player_messaged = false;
int source_power, target_power, relax;
+ int tries = 1000;
UNUSED( power );
@@ -883,9 +903,15 @@ bool monster_polymorph( struct monsters *monster, int targetc, int power )
if (relax > 50)
return (simple_monster_message( monster, " shudders." ));
}
- while (!valid_morph( monster, targetc )
+ while (tries-- && (!valid_morph( monster, targetc )
|| target_power < source_power - relax
- || target_power > source_power + (relax * 3) / 2);
+ || target_power > source_power + (relax * 3) / 2));
+ }
+
+ if(!valid_morph( monster, targetc )) {
+ strcat( str_polymon, " looks momentarily different.");
+ player_messaged = simple_monster_message( monster, str_polymon );
+ return (player_messaged);
}
// messaging: {dlb}
@@ -1530,7 +1556,7 @@ static void handle_behaviour(struct monsters *mon)
}
// hack: smarter monsters will
- // tend to persue the player longer.
+ // tend to pursue the player longer.
int memory;
switch(mons_intel(monster_index(mon)))
{
@@ -3715,6 +3741,9 @@ static bool handle_pickup(struct monsters *monster)
if (mons_has_ench( monster, ENCH_SUBMERGED ))
return (false);
+ if (monster->behaviour == BEH_SLEEP)
+ return (false);
+
if (monster->type == MONS_JELLY
|| monster->type == MONS_BROWN_OOZE
|| monster->type == MONS_ACID_BLOB
@@ -4577,6 +4606,7 @@ static void mons_in_cloud(struct monsters *monster)
struct bolt beam;
const int speed = ((monster->speed > 0) ? monster->speed : 10);
+ bool wake = false;
if (mons_is_mimic( monster->type ))
{
@@ -4654,6 +4684,8 @@ static void mons_in_cloud(struct monsters *monster)
return;
poison_monster(monster, (env.cloud[wc].type == CLOUD_POISON));
+ // If the monster got poisoned, wake it up.
+ wake = true;
hurted += (random2(8) * 10) / speed;
@@ -4706,6 +4738,14 @@ static void mons_in_cloud(struct monsters *monster)
return;
}
+ // A sleeping monster that sustains damage will wake up.
+ if ((wake || hurted > 0) && monster->behaviour == BEH_SLEEP)
+ {
+ // We have no good coords to give the monster as the source of the
+ // disturbance other than the cloud itself.
+ behaviour_event(monster, ME_DISTURB, MHITNOT, monster->x, monster->y);
+ }
+
if (hurted < 0)
hurted = 0;
else if (hurted > 0)
diff --git a/trunk/source/mt19937ar.cc b/trunk/source/mt19937ar.cc
new file mode 100644
index 0000000000..2b5697aeb5
--- /dev/null
+++ b/trunk/source/mt19937ar.cc
@@ -0,0 +1,229 @@
+/*
+ A C-program for MT19937, with initialization improved 2002/1/26.
+ Coded by Takuji Nishimura and Makoto Matsumoto.
+
+ Before using, initialize the state by using init_genrand(seed)
+ or init_by_array(init_key, key_length).
+
+ Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. The names of its contributors may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+ Any feedback is very welcome.
+ http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
+ email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
+*/
+
+#include <stdio.h>
+#include <string.h>
+#include <stack>
+
+/* Period parameters */
+#define N 624
+#define M 397
+#define MATRIX_A 0x9908b0dfUL /* constant vector a */
+#define UPPER_MASK 0x80000000UL /* most significant w-r bits */
+#define LOWER_MASK 0x7fffffffUL /* least significant r bits */
+
+static unsigned long mt[N]; /* the array for the state vector */
+static int mti=N+1; /* mti==N+1 means mt[N] is not initialized */
+
+struct mtrng_state
+{
+ int mti;
+ unsigned long mt[N];
+};
+
+static std::stack<mtrng_state*> states;
+
+void push_mt_state()
+{
+ // We're doing a new, delete and a lot of copying for saving/restoring
+ // state. Slow.
+ mtrng_state *mtst = new mtrng_state;
+ if (!mtst)
+ return ;
+ mtst->mti = mti;
+ memcpy(mtst->mt, mt, sizeof(mt));
+ states.push(mtst);
+}
+
+void pop_mt_state()
+{
+ if (states.empty())
+ return;
+ mtrng_state *mtst = states.top();
+ states.pop();
+
+ mti = mtst->mti;
+ memcpy(mt, mtst->mt, sizeof(mt));
+
+ delete mtst;
+}
+
+/* initializes mt[N] with a seed */
+void init_genrand(unsigned long s)
+{
+ mt[0]= s & 0xffffffffUL;
+ for (mti=1; mti<N; mti++) {
+ mt[mti] =
+ (1812433253UL * (mt[mti-1] ^ (mt[mti-1] >> 30)) + mti);
+ /* See Knuth TAOCP Vol2. 3rd Ed. P.106 for multiplier. */
+ /* In the previous versions, MSBs of the seed affect */
+ /* only MSBs of the array mt[]. */
+ /* 2002/01/09 modified by Makoto Matsumoto */
+ mt[mti] &= 0xffffffffUL;
+ /* for >32 bit machines */
+ }
+}
+
+#if 0
+/* initialize by an array with array-length */
+/* init_key is the array for initializing keys */
+/* key_length is its length */
+/* slight change for C++, 2004/2/26 */
+void init_by_array(unsigned long init_key[], int key_length)
+{
+ int i, j, k;
+ init_genrand(19650218UL);
+ i=1; j=0;
+ k = (N>key_length ? N : key_length);
+ for (; k; k--) {
+ mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1664525UL))
+ + init_key[j] + j; /* non linear */
+ mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
+ i++; j++;
+ if (i>=N) { mt[0] = mt[N-1]; i=1; }
+ if (j>=key_length) j=0;
+ }
+ for (k=N-1; k; k--) {
+ mt[i] = (mt[i] ^ ((mt[i-1] ^ (mt[i-1] >> 30)) * 1566083941UL))
+ - i; /* non linear */
+ mt[i] &= 0xffffffffUL; /* for WORDSIZE > 32 machines */
+ i++;
+ if (i>=N) { mt[0] = mt[N-1]; i=1; }
+ }
+
+ mt[0] = 0x80000000UL; /* MSB is 1; assuring non-zero initial array */
+}
+#endif
+
+/* generates a random number on [0,0xffffffff]-interval */
+unsigned long genrand_int32(void)
+{
+ unsigned long y;
+ static unsigned long mag01[2]={0x0UL, MATRIX_A};
+ /* mag01[x] = x * MATRIX_A for x=0,1 */
+
+ if (mti >= N) { /* generate N words at one time */
+ int kk;
+
+ if (mti == N+1) /* if init_genrand() has not been called, */
+ init_genrand(5489UL); /* a default initial seed is used */
+
+ for (kk=0;kk<N-M;kk++) {
+ y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
+ mt[kk] = mt[kk+M] ^ (y >> 1) ^ mag01[y & 0x1UL];
+ }
+ for (;kk<N-1;kk++) {
+ y = (mt[kk]&UPPER_MASK)|(mt[kk+1]&LOWER_MASK);
+ mt[kk] = mt[kk+(M-N)] ^ (y >> 1) ^ mag01[y & 0x1UL];
+ }
+ y = (mt[N-1]&UPPER_MASK)|(mt[0]&LOWER_MASK);
+ mt[N-1] = mt[M-1] ^ (y >> 1) ^ mag01[y & 0x1UL];
+
+ mti = 0;
+ }
+
+ y = mt[mti++];
+
+ /* Tempering */
+ y ^= (y >> 11);
+ y ^= (y << 7) & 0x9d2c5680UL;
+ y ^= (y << 15) & 0xefc60000UL;
+ y ^= (y >> 18);
+
+ return y;
+}
+
+#if 0
+/* generates a random number on [0,0x7fffffff]-interval */
+long genrand_int31(void)
+{
+ return (long)(genrand_int32()>>1);
+}
+
+/* generates a random number on [0,1]-real-interval */
+double genrand_real1(void)
+{
+ return genrand_int32()*(1.0/4294967295.0);
+ /* divided by 2^32-1 */
+}
+
+/* generates a random number on [0,1)-real-interval */
+double genrand_real2(void)
+{
+ return genrand_int32()*(1.0/4294967296.0);
+ /* divided by 2^32 */
+}
+
+/* generates a random number on (0,1)-real-interval */
+double genrand_real3(void)
+{
+ return (((double)genrand_int32()) + 0.5)*(1.0/4294967296.0);
+ /* divided by 2^32 */
+}
+
+/* generates a random number on [0,1) with 53-bit resolution*/
+double genrand_res53(void)
+{
+ unsigned long a=genrand_int32()>>5, b=genrand_int32()>>6;
+ return(a*67108864.0+b)*(1.0/9007199254740992.0);
+}
+/* These real versions are due to Isaku Wada, 2002/01/09 added */
+
+int main(void)
+{
+ int i;
+ unsigned long init[4]={0x123, 0x234, 0x345, 0x456}, length=4;
+ init_by_array(init, length);
+ printf("1000 outputs of genrand_int32()\n");
+ for (i=0; i<1000; i++) {
+ printf("%10lu ", genrand_int32());
+ if (i%5==4) printf("\n");
+ }
+ printf("\n1000 outputs of genrand_real2()\n");
+ for (i=0; i<1000; i++) {
+ printf("%10.8f ", genrand_real2());
+ if (i%5==4) printf("\n");
+ }
+ return 0;
+}
+#endif
diff --git a/trunk/source/mt19937ar.h b/trunk/source/mt19937ar.h
new file mode 100644
index 0000000000..3e8948ee6a
--- /dev/null
+++ b/trunk/source/mt19937ar.h
@@ -0,0 +1,52 @@
+/*
+ A C-program for MT19937, with initialization improved 2002/1/26.
+ Coded by Takuji Nishimura and Makoto Matsumoto.
+
+ Before using, initialize the state by using init_genrand(seed)
+ or init_by_array(init_key, key_length).
+
+ Copyright (C) 1997 - 2002, Makoto Matsumoto and Takuji Nishimura,
+ All rights reserved.
+
+ Redistribution and use in source and binary forms, with or without
+ modification, are permitted provided that the following conditions
+ are met:
+
+ 1. Redistributions of source code must retain the above copyright
+ notice, this list of conditions and the following disclaimer.
+
+ 2. Redistributions in binary form must reproduce the above copyright
+ notice, this list of conditions and the following disclaimer in the
+ documentation and/or other materials provided with the distribution.
+
+ 3. The names of its contributors may not be used to endorse or promote
+ products derived from this software without specific prior written
+ permission.
+
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+ "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+ LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+ A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
+ CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
+ EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+ PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
+ PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
+ LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
+ NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+ Any feedback is very welcome.
+ http://www.math.sci.hiroshima-u.ac.jp/~m-mat/MT/emt.html
+ email: m-mat @ math.sci.hiroshima-u.ac.jp (remove space)
+*/
+
+/* initializes mt[N] with a seed */
+void init_genrand( unsigned long s );
+
+/* generates a random number on [0,0xffffffff]-interval */
+unsigned long genrand_int32( void );
+
+void push_mt_state();
+
+void pop_mt_state();
diff --git a/trunk/source/newgame.cc b/trunk/source/newgame.cc
index 9d14061805..6316209b4a 100644
--- a/trunk/source/newgame.cc
+++ b/trunk/source/newgame.cc
@@ -84,6 +84,7 @@
#include "dungeon.h"
#include "files.h"
#include "fight.h"
+#include "initfile.h"
#include "itemname.h"
#include "items.h"
#include "macro.h"
@@ -124,6 +125,85 @@ static void create_wanderer(void);
static void give_items_skills(void);
static bool choose_race(void);
static bool choose_class(void);
+static char letter_to_species(int keyn);
+static char letter_to_class(int keyn);
+
+////////////////////////////////////////////////////////////////////////
+// Remember player's startup options
+//
+
+static char ng_race, ng_cls;
+static bool ng_random;
+static int ng_ck, ng_dk, ng_pr;
+static int ng_weapon;
+
+static void reset_newgame_options(void)
+{
+ ng_race = ng_cls = 0;
+ ng_random = false;
+ ng_ck = GOD_NO_GOD;
+ ng_dk = DK_NO_SELECTION;
+ ng_pr = GOD_NO_GOD;
+ ng_weapon = WPN_UNKNOWN;
+}
+
+static void save_newgame_options(void)
+{
+ // Note that we store race and class directly here, whereas up until
+ // now we've been storing the hotkey.
+ Options.prev_race = ng_race;
+ Options.prev_cls = ng_cls;
+ Options.prev_randpick = ng_random;
+ Options.prev_ck = ng_ck;
+ Options.prev_dk = ng_dk;
+ Options.prev_pr = ng_pr;
+ Options.prev_weapon = ng_weapon;
+
+ write_newgame_options_file();
+}
+
+static void set_startup_options(void)
+{
+ Options.race = Options.prev_race;
+ Options.cls = Options.prev_cls;
+ Options.weapon = Options.prev_weapon;
+ Options.death_knight = Options.prev_dk;
+ Options.chaos_knight = Options.prev_ck;
+ Options.priest = Options.prev_pr;
+}
+
+static bool prev_startup_options_set(void)
+{
+ // Are these two enough? They should be, in theory, since we'll
+ // remember the player's weapon and god choices.
+ return Options.prev_race && Options.prev_cls;
+}
+
+static std::string get_opt_race_name(char race)
+{
+ int prace = letter_to_species(race);
+ return prace? species_name(prace, 1) : "Random";
+}
+
+static std::string get_opt_class_name(char oclass)
+{
+ int pcls = letter_to_class(oclass);
+ return pcls != JOB_UNKNOWN? get_class_name(pcls) : "Random";
+}
+
+static std::string prev_startup_description(void)
+{
+ if (Options.prev_race == '?' && Options.prev_cls == '?')
+ Options.prev_randpick = true;
+
+ if (Options.prev_randpick)
+ return "Random character";
+
+ if (Options.prev_cls == '?')
+ return "Random " + get_opt_race_name(Options.prev_race);
+ return get_opt_race_name(Options.prev_race) + " " +
+ get_opt_class_name(Options.prev_cls);
+}
int give_first_conjuration_book()
{
@@ -309,24 +389,20 @@ bool new_game(void)
cprintf( "!" );
textcolor( LIGHTGREY );
+ save_player_name();
return (false);
}
}
+ reset_newgame_options();
if (Options.random_pick)
{
pick_random_species_and_class();
+ ng_random = true;
}
else
{
- bool keep_going = true;
- while (keep_going)
- {
- if (choose_race())
- keep_going = !choose_class();
- else
- keep_going = false;
- }
+ while (choose_race() && !choose_class());
}
strcpy( you.class_name, get_class_name( you.char_class ) );
@@ -688,7 +764,14 @@ bool new_game(void)
{
item_colour( you.inv[i] ); // set correct special and colour
}
+
+ if (is_valid_item(you.inv[i]))
+ {
+ you.inv[i].slot = index_to_letter(you.inv[i].link);
+ }
}
+ // Brand items as original equipment.
+ origin_set_inventory(origin_set_startequip);
// we calculate hp and mp here; all relevant factors should be
// finalized by now (GDL)
@@ -750,6 +833,7 @@ bool new_game(void)
you.branch_stairs[STAIRS_HALL_OF_ZOT] = 26; // always 26
+ save_newgame_options();
return (true);
} // end of new_game()
@@ -1375,6 +1459,7 @@ void choose_weapon( void )
&& (Options.weapon != WPN_TRIDENT || num_choices == 5))
{
you.inv[0].sub_type = Options.weapon;
+ ng_weapon = Options.weapon;
return;
}
@@ -1386,6 +1471,7 @@ void choose_weapon( void )
cprintf(EOL " You have a choice of weapons:" EOL);
textcolor( LIGHTGREY );
+ bool prevmatch = false;
for(int i=0; i<num_choices; i++)
{
int x = effective_stat_bonus(startwep[i]);
@@ -1395,10 +1481,26 @@ void choose_weapon( void )
(x <= -4) ? " (not ideal)" : "" );
cprintf(info);
- }
- cprintf(EOL "? - Random" EOL);
+ if (Options.prev_weapon == startwep[i])
+ prevmatch = true;
+ }
+ if (!prevmatch && Options.prev_weapon != WPN_RANDOM)
+ Options.prev_weapon = WPN_UNKNOWN;
+ textcolor(BROWN);
+ cprintf(EOL "? - Random" );
+ if (Options.prev_weapon != WPN_UNKNOWN)
+ {
+ char weapbuf[ITEMNAME_SIZE];
+ if (Options.prev_weapon != WPN_RANDOM)
+ standard_name_weap(Options.prev_weapon, weapbuf);
+ cprintf("; Enter - %s",
+ Options.prev_weapon == WPN_RANDOM? "Random" :
+ weapbuf);
+ }
+ cprintf(EOL);
+
do
{
textcolor( CYAN );
@@ -1407,7 +1509,22 @@ void choose_weapon( void )
keyin = get_ch();
}
- while (keyin != '?' && (keyin < 'a' || keyin > ('a' + num_choices)));
+ while (keyin != '?' &&
+ ((keyin != '\r' && keyin != '\n')
+ || Options.prev_weapon == WPN_UNKNOWN) &&
+ (keyin < 'a' || keyin > ('a' + num_choices)));
+
+ if (keyin == '\r' || keyin == '\n')
+ {
+ if (Options.prev_weapon == WPN_RANDOM)
+ keyin = '?';
+ else
+ {
+ for (int i = 0; i < num_choices; ++i)
+ if (startwep[i] == Options.prev_weapon)
+ keyin = 'a' + i;
+ }
+ }
if (keyin != '?' && effective_stat_bonus(startwep[keyin-'a']) > -4)
cprintf(EOL "A fine choice. " EOL);
@@ -1415,6 +1532,7 @@ void choose_weapon( void )
if (Options.random_pick || Options.weapon == WPN_RANDOM || keyin == '?')
{
+ Options.weapon = WPN_RANDOM;
// try to choose a decent weapon
for(int times=0; times<50; times++)
{
@@ -1427,6 +1545,10 @@ void choose_weapon( void )
}
you.inv[0].sub_type = startwep[keyin-'a'];
+ ng_weapon = (Options.random_pick ||
+ Options.weapon == WPN_RANDOM ||
+ keyin == '?')
+ ? WPN_RANDOM : you.inv[0].sub_type;
}
void init_player(void)
@@ -1443,6 +1565,7 @@ void init_player(void)
you.wizard = false;
#endif
+ you.activity = ACT_NONE;
you.berserk_penalty = 0;
you.berserker = 0;
you.conf = 0;
@@ -1479,7 +1602,7 @@ void init_player(void)
you.experience = 0;
you.experience_level = 1;
you.max_level = 1;
- you.char_class = JOB_FIGHTER;
+ you.char_class = JOB_UNKNOWN;
you.hunger = 6000;
you.hunger_state = HS_SATIATED;
@@ -1506,6 +1629,9 @@ void init_player(void)
you.running = 0;
you.run_x = 0;
you.run_y = 0;
+ you.travel_x = 0;
+ you.travel_y = 0;
+
for (i = 0; i < 3; i++)
{
you.run_check[i].grid = 0;
@@ -1913,7 +2039,15 @@ void enterPlayerName(bool blankOK)
{
textcolor( CYAN );
if (blankOK && first_time)
- cprintf(EOL "Press <Enter> to answer this after race and class are chosen."EOL);
+ {
+ if (Options.prev_name.length() && Options.remember_name)
+ cprintf(EOL "Press <Enter> for \"%s\"." EOL,
+ Options.prev_name.c_str());
+ else
+ cprintf(EOL
+ "Press <Enter> to answer this after race and "
+ "class are chosen." EOL);
+ }
first_time = false;
@@ -1925,6 +2059,21 @@ void enterPlayerName(bool blankOK)
you.your_name[ kNameLen - 1 ] = '\0';
}
+ if (!*you.your_name && blankOK && Options.prev_name.length() &&
+ Options.remember_name)
+ {
+ strncpy(you.your_name, Options.prev_name.c_str(), kNameLen);
+ you.your_name[kNameLen - 1] = 0;
+ }
+
+ // '.', '?' and '*' are blanked.
+ if (!you.your_name[1] && (*you.your_name == '.' ||
+ *you.your_name == '*' ||
+ *you.your_name == '?'))
+ {
+ *you.your_name = 0;
+ }
+
// verification begins here {dlb}:
if (you.your_name[0] == '\0')
{
@@ -2571,6 +2720,137 @@ static void create_wanderer( void )
you.equip[EQ_BODY_ARMOUR] = 2;
}
+static char letter_to_class(int keyn)
+{
+ if (keyn == 'a')
+ return JOB_FIGHTER;
+ else if (keyn == 'b')
+ return JOB_WIZARD;
+ else if (keyn == 'c')
+ return JOB_PRIEST;
+ else if (keyn == 'd')
+ return JOB_THIEF;
+ else if (keyn == 'e')
+ return JOB_GLADIATOR;
+ else if (keyn == 'f')
+ return JOB_NECROMANCER;
+ else if (keyn == 'g')
+ return JOB_PALADIN;
+ else if (keyn == 'h')
+ return JOB_ASSASSIN;
+ else if (keyn == 'i')
+ return JOB_BERSERKER;
+ else if (keyn == 'j')
+ return JOB_HUNTER;
+ else if (keyn == 'k')
+ return JOB_CONJURER;
+ else if (keyn == 'l')
+ return JOB_ENCHANTER;
+ else if (keyn == 'm')
+ return JOB_FIRE_ELEMENTALIST;
+ else if (keyn == 'n')
+ return JOB_ICE_ELEMENTALIST;
+ else if (keyn == 'o')
+ return JOB_SUMMONER;
+ else if (keyn == 'p')
+ return JOB_AIR_ELEMENTALIST;
+ else if (keyn == 'q')
+ return JOB_EARTH_ELEMENTALIST;
+ else if (keyn == 'r')
+ return JOB_CRUSADER;
+ else if (keyn == 's')
+ return JOB_DEATH_KNIGHT;
+ else if (keyn == 't')
+ return JOB_VENOM_MAGE;
+ else if (keyn == 'u')
+ return JOB_CHAOS_KNIGHT;
+ else if (keyn == 'v')
+ return JOB_TRANSMUTER;
+ else if (keyn == 'w')
+ return JOB_HEALER;
+ else if (keyn == 'y')
+ return JOB_REAVER;
+ else if (keyn == 'z')
+ return JOB_STALKER;
+ else if (keyn == 'A')
+ return JOB_MONK;
+ else if (keyn == 'B')
+ return JOB_WARPER;
+ else if (keyn == 'C')
+ return JOB_WANDERER;
+ return JOB_UNKNOWN;
+}
+
+static char letter_to_species(int keyn)
+{
+ switch (keyn)
+ {
+ case 'a':
+ return SP_HUMAN;
+ case 'b':
+ return SP_ELF;
+ case 'c':
+ return SP_HIGH_ELF;
+ case 'd':
+ return SP_GREY_ELF;
+ case 'e':
+ return SP_DEEP_ELF;
+ case 'f':
+ return SP_SLUDGE_ELF;
+ case 'g':
+ return SP_HILL_DWARF;
+ case 'h':
+ return SP_MOUNTAIN_DWARF;
+ case 'i':
+ return SP_HALFLING;
+ case 'j':
+ return SP_HILL_ORC;
+ case 'k':
+ return SP_KOBOLD;
+ case 'l':
+ return SP_MUMMY;
+ case 'm':
+ return SP_NAGA;
+ case 'n':
+ return SP_GNOME;
+ case 'o':
+ return SP_OGRE;
+ case 'p':
+ return SP_TROLL;
+ case 'q':
+ return SP_OGRE_MAGE;
+ case 'r': // draconian
+ return SP_RED_DRACONIAN + random2(9); // random drac
+ case 's':
+ return SP_CENTAUR;
+ case 't':
+ return SP_DEMIGOD;
+ case 'u':
+ return SP_SPRIGGAN;
+ case 'v':
+ return SP_MINOTAUR;
+ case 'w':
+ return SP_DEMONSPAWN;
+ case 'x':
+ return SP_GHOUL;
+ case 'y':
+ return SP_KENKU;
+ case 'z':
+ return SP_MERFOLK;
+ default:
+ return 0;
+ }
+}
+
+static char species_to_letter(int spec)
+{
+ if (spec > SP_RED_DRACONIAN && spec <= SP_UNK2_DRACONIAN)
+ spec = SP_RED_DRACONIAN;
+ else if (spec > SP_UNK2_DRACONIAN)
+ spec -= SP_UNK2_DRACONIAN - SP_RED_DRACONIAN;
+ return 'a' + spec - 1;
+}
+
// choose_race returns true if the player should also pick a class.
// This is done because of the '*' option which will pick a random
// character, obviating the necessity of choosing a class.
@@ -2580,38 +2860,110 @@ bool choose_race()
char keyn;
bool printed = false;
+
+ if (Options.cls)
+ {
+ you.char_class = letter_to_class(Options.cls);
+ ng_cls = Options.cls;
+ }
+
if (Options.race != 0)
printed = true;
spec_query:
+ bool prevraceok = Options.prev_race == '?';
if (!printed)
{
clrscr();
- textcolor( WHITE );
- cprintf("You must be new here!" EOL EOL);
+ if (you.char_class != JOB_UNKNOWN)
+ {
+ textcolor( BROWN );
+ bool shortgreet = false;
+ if (strlen(you.your_name) || you.char_class != JOB_UNKNOWN)
+ cprintf("Welcome, ");
+ else
+ {
+ cprintf("Welcome.");
+ shortgreet = true;
+ }
+
+ textcolor( YELLOW );
+ if (strlen(you.your_name) > 0)
+ {
+ cprintf(you.your_name);
+ if (you.char_class != JOB_UNKNOWN)
+ cprintf(" the ");
+ }
+ if (you.char_class != JOB_UNKNOWN)
+ cprintf(get_class_name(you.char_class));
+ if (!shortgreet)
+ cprintf(".");
+ }
+ else
+ {
+ textcolor( WHITE );
+ cprintf("You must be new here!");
+ }
+ cprintf(EOL EOL);
textcolor( CYAN );
- cprintf("You can be:" EOL EOL);
+ cprintf("You can be:");
+ cprintf(EOL EOL);
+
textcolor( LIGHTGREY );
- cprintf("a - Human b - Elf" EOL);
- cprintf("c - High Elf d - Grey Elf" EOL);
- cprintf("e - Deep Elf f - Sludge Elf" EOL);
- cprintf("g - Hill Dwarf h - Mountain Dwarf" EOL);
- cprintf("i - Halfling j - Hill Orc" EOL);
- cprintf("k - Kobold l - Mummy" EOL);
- cprintf("m - Naga n - Gnome" EOL);
- cprintf("o - Ogre p - Troll" EOL);
- cprintf("q - Ogre-Mage r - Draconian" EOL);
- cprintf("s - Centaur t - Demigod" EOL);
- cprintf("u - Spriggan v - Minotaur" EOL);
- cprintf("w - Demonspawn x - Ghoul" EOL);
- cprintf("y - Kenku z - Merfolk" EOL);
+ int linec = 0;
+ char linebuf[200];
+ *linebuf = 0;
+ for (int i = SP_HUMAN; i < NUM_SPECIES; ++i)
+ {
+ if (i > SP_RED_DRACONIAN && i <= SP_UNK2_DRACONIAN)
+ continue;
+
+ if (you.char_class != JOB_UNKNOWN &&
+ !class_allowed(i, you.char_class))
+ continue;
+
+ char buf[100];
+ char sletter = species_to_letter(i);
+ snprintf(buf, sizeof buf, "%c - %-26s",
+ sletter,
+ species_name(i, 1));
+ if (sletter == Options.prev_race)
+ prevraceok = true;
+ strncat(linebuf, buf, sizeof linebuf);
+ if (++linec >= 2)
+ {
+ cprintf("%s" EOL, linebuf);
+ *linebuf = 0;
+ linec = 0;
+ }
+ }
+
+ if (linec)
+ cprintf("%s" EOL, linebuf);
textcolor( BROWN );
- cprintf(EOL "? - Random Species * - Random Character" EOL);
- cprintf( "X - Quit" EOL);
+
+ if (you.char_class == JOB_UNKNOWN)
+ cprintf(EOL "SPACE - Choose class first; "
+ "? - Random Species; * - Random Character; X - Quit"
+ EOL);
+ else
+ cprintf(EOL "? - Random; Bksp - Back to class selection; X - Quit"
+ EOL);
+
+ if (Options.prev_race)
+ {
+ if (prevraceok)
+ cprintf("Enter - %s", get_opt_race_name(Options.prev_race).c_str());
+ if (prev_startup_options_set())
+ cprintf("%sTAB - %s",
+ prevraceok? "; " : "",
+ prev_startup_description().c_str());
+ cprintf(EOL);
+ }
textcolor( CYAN );
cprintf(EOL "Which one? ");
@@ -2626,119 +2978,75 @@ spec_query:
}
else
{
- keyn = getch();
- if (keyn == 0)
+ keyn = c_getch();
+ }
+
+ if ((keyn == '\r' || keyn == '\n') && Options.prev_race && prevraceok)
+ keyn = Options.prev_race;
+
+ if (keyn == '\t' && prev_startup_options_set())
+ {
+ if (Options.prev_randpick ||
+ (Options.prev_race == '?' && Options.prev_cls == '?'))
{
- getch();
- goto spec_query;
+ Options.random_pick = true;
+ ng_random = true;
+ pick_random_species_and_class();
+ return false;
}
+ set_startup_options();
+ you.species = 0;
+ you.char_class = JOB_UNKNOWN;
+ return true;
}
+ if (keyn == CK_BKSP || keyn == ' ')
+ {
+ you.species = 0;
+ Options.race = 0;
+ return true;
+ }
+
+ bool randrace = (keyn == '?');
if (keyn == '?')
- keyn = 'a' + random2(26);
+ {
+ do
+ keyn = 'a' + random2(26);
+ while (you.char_class != JOB_UNKNOWN &&
+ !class_allowed(letter_to_species(keyn), you.char_class));
+ }
else if (keyn == '*')
{
pick_random_species_and_class();
Options.random_pick = true; // used to give random weapon/god as well
+ ng_random = true;
return false;
}
-
- switch (keyn)
+ if (!(you.species = letter_to_species(keyn)))
{
- case 'a':
- you.species = SP_HUMAN;
- break;
- case 'b':
- you.species = SP_ELF;
- break;
- case 'c':
- you.species = SP_HIGH_ELF;
- break;
- case 'd':
- you.species = SP_GREY_ELF;
- break;
- case 'e':
- you.species = SP_DEEP_ELF;
- break;
- case 'f':
- you.species = SP_SLUDGE_ELF;
- break;
- case 'g':
- you.species = SP_HILL_DWARF;
- break;
- case 'h':
- you.species = SP_MOUNTAIN_DWARF;
- break;
- case 'i':
- you.species = SP_HALFLING;
- break;
- case 'j':
- you.species = SP_HILL_ORC;
- break;
- case 'k':
- you.species = SP_KOBOLD;
- break;
- case 'l':
- you.species = SP_MUMMY;
- break;
- case 'm':
- you.species = SP_NAGA;
- break;
- case 'n':
- you.species = SP_GNOME;
- break;
- case 'o':
- you.species = SP_OGRE;
- break;
- case 'p':
- you.species = SP_TROLL;
- break;
- case 'q':
- you.species = SP_OGRE_MAGE;
- break;
- case 'r': // draconian
- you.species = SP_RED_DRACONIAN + random2(9); // random drac
- break;
- case 's':
- you.species = SP_CENTAUR;
- break;
- case 't':
- you.species = SP_DEMIGOD;
- break;
- case 'u':
- you.species = SP_SPRIGGAN;
- break;
- case 'v':
- you.species = SP_MINOTAUR;
- break;
- case 'w':
- you.species = SP_DEMONSPAWN;
- break;
- case 'x':
- you.species = SP_GHOUL;
- break;
- case 'y':
- you.species = SP_KENKU;
- break;
- case 'z':
- you.species = SP_MERFOLK;
- break;
- case 'X':
- cprintf(EOL "Goodbye!");
- end(0);
- break;
- default:
- if (Options.race != 0)
+ switch (keyn)
{
- Options.race = 0;
- printed = false;
+ case 'X':
+ cprintf(EOL "Goodbye!");
+ end(0);
+ break;
+ default:
+ if (Options.race != 0)
+ {
+ Options.race = 0;
+ printed = false;
+ }
+ goto spec_query;
}
- goto spec_query;
}
+ if (you.species && you.char_class != JOB_UNKNOWN
+ && !class_allowed(you.species, you.char_class))
+ goto spec_query;
// set to 0 in case we come back from choose_class()
Options.race = 0;
+ ng_race = randrace? '?' : keyn;
return true;
}
@@ -2754,54 +3062,105 @@ bool choose_class(void)
bool printed = false;
if (Options.cls != 0)
printed = true;
+
+ if (you.species && you.char_class != JOB_UNKNOWN)
+ return true;
+
+ ng_cls = 0;
+
job_query:
+ bool prevclassok = Options.prev_cls == '?';
if (!printed)
{
clrscr();
- textcolor( BROWN );
- cprintf(EOL EOL);
- cprintf("Welcome, ");
- textcolor( YELLOW );
- if (strlen(you.your_name) > 0)
+ if (you.species)
{
- cprintf(you.your_name);
- cprintf(" the ");
+ textcolor( BROWN );
+ bool shortgreet = false;
+ if (strlen(you.your_name) || you.species)
+ cprintf("Welcome, ");
+ else
+ {
+ cprintf("Welcome.");
+ shortgreet = true;
+ }
+
+ textcolor( YELLOW );
+ if (strlen(you.your_name) > 0)
+ {
+ cprintf(you.your_name);
+ if (you.species)
+ cprintf(" the ");
+ }
+ if (you.species)
+ cprintf(species_name(you.species,you.experience_level));
+
+ if (!shortgreet)
+ cprintf(".");
+ }
+ else
+ {
+ textcolor( WHITE );
+ cprintf("You must be new here!");
}
- cprintf(species_name(you.species,you.experience_level));
- cprintf("." EOL EOL);
+ cprintf(EOL EOL);
textcolor( CYAN );
- cprintf("You can be any of the following :" EOL);
+ cprintf("You can be:");
+ cprintf(EOL EOL);
+
textcolor( LIGHTGREY );
j = 0; // used within for loop to determine newline {dlb}
for (i = 0; i < NUM_JOBS; i++)
{
- if (!class_allowed(you.species, i))
+ if (you.species? !class_allowed(you.species, i) : i == JOB_QUITTER)
continue;
- putch( index_to_letter(i) );
+ char letter = index_to_letter(i);
+
+ if (letter == Options.prev_cls)
+ prevclassok = true;
+
+ putch( letter );
cprintf( " - " );
cprintf( get_class_name(i) );
if (j % 2)
cprintf(EOL);
else
- gotoxy(40, wherey());
+ gotoxy(31, wherey());
j++;
}
- if (wherex() >= 40)
+ if (j % 2)
cprintf(EOL);
textcolor( BROWN );
- cprintf(EOL "? - Random; x - Back to species selection; X - Quit" EOL);
+ if (!you.species)
+ cprintf(EOL "SPACE - Choose species first; "
+ "? - Random Class; * - Random Character; X - Quit"
+ EOL);
+ else
+ cprintf(EOL "? - Random; Bksp - Back to species selection; X - Quit"
+ EOL);
+ if (Options.prev_cls)
+ {
+ if (prevclassok)
+ cprintf("Enter - %s", get_opt_class_name(Options.prev_cls).c_str());
+ if (prev_startup_options_set())
+ cprintf("%sTAB - %s",
+ prevclassok? "; " : "",
+ prev_startup_description().c_str());
+ cprintf(EOL);
+ }
+
textcolor( CYAN );
- cprintf(EOL "What kind of character are you? ");
+ cprintf(EOL "Which one? ");
textcolor( LIGHTGREY );
printed = true;
@@ -2813,110 +3172,86 @@ job_query:
}
else
{
- keyn = getch();
- if (keyn == 0)
- {
- getch();
- goto job_query;
- }
+ keyn = c_getch();
}
- if (keyn == 'a')
- you.char_class = JOB_FIGHTER;
- else if (keyn == 'b')
- you.char_class = JOB_WIZARD;
- else if (keyn == 'c')
- you.char_class = JOB_PRIEST;
- else if (keyn == 'd')
- you.char_class = JOB_THIEF;
- else if (keyn == 'e')
- you.char_class = JOB_GLADIATOR;
- else if (keyn == 'f')
- you.char_class = JOB_NECROMANCER;
- else if (keyn == 'g')
- you.char_class = JOB_PALADIN;
- else if (keyn == 'h')
- you.char_class = JOB_ASSASSIN;
- else if (keyn == 'i')
- you.char_class = JOB_BERSERKER;
- else if (keyn == 'j')
- you.char_class = JOB_HUNTER;
- else if (keyn == 'k')
- you.char_class = JOB_CONJURER;
- else if (keyn == 'l')
- you.char_class = JOB_ENCHANTER;
- else if (keyn == 'm')
- you.char_class = JOB_FIRE_ELEMENTALIST;
- else if (keyn == 'n')
- you.char_class = JOB_ICE_ELEMENTALIST;
- else if (keyn == 'o')
- you.char_class = JOB_SUMMONER;
- else if (keyn == 'p')
- you.char_class = JOB_AIR_ELEMENTALIST;
- else if (keyn == 'q')
- you.char_class = JOB_EARTH_ELEMENTALIST;
- else if (keyn == 'r')
- you.char_class = JOB_CRUSADER;
- else if (keyn == 's')
- you.char_class = JOB_DEATH_KNIGHT;
- else if (keyn == 't')
- you.char_class = JOB_VENOM_MAGE;
- else if (keyn == 'u')
- you.char_class = JOB_CHAOS_KNIGHT;
- else if (keyn == 'v')
- you.char_class = JOB_TRANSMUTER;
- else if (keyn == 'w')
- you.char_class = JOB_HEALER;
- else if (keyn == 'y')
- you.char_class = JOB_REAVER;
- else if (keyn == 'z')
- you.char_class = JOB_STALKER;
- else if (keyn == 'A')
- you.char_class = JOB_MONK;
- else if (keyn == 'B')
- you.char_class = JOB_WARPER;
- else if (keyn == 'C')
- you.char_class = JOB_WANDERER;
- else if (keyn == '?')
- {
- // pick a job at random... see god retribution for proof this
- // is uniform. -- bwr
- int job_count = 0;
- int job = -1;
+ if ((keyn == '\r' || keyn == '\n') && Options.prev_cls && prevclassok)
+ keyn = Options.prev_cls;
- for (int i = 0; i < NUM_JOBS; i++)
+ if (keyn == '\t' && prev_startup_options_set())
+ {
+ if (Options.prev_randpick ||
+ (Options.prev_race == '?' && Options.prev_cls == '?'))
{
- if (class_allowed(you.species, i))
- {
- job_count++;
- if (one_chance_in( job_count ))
- job = i;
- }
+ Options.random_pick = true;
+ ng_random = true;
+ pick_random_species_and_class();
+ return true;
}
+ set_startup_options();
+
+ // Toss out old species selection, if any.
+ you.species = 0;
+ you.char_class = JOB_UNKNOWN;
- ASSERT( job != -1 ); // at least one class should have been allowed
- you.char_class = job;
- }
- else if (keyn == 'x' || keyn == ESCAPE)
- {
return false;
}
- else if (keyn == 'X')
- {
- cprintf(EOL "Goodbye!");
- end(0);
- }
- else
+
+ if ((you.char_class = letter_to_class(keyn)) == JOB_UNKNOWN)
{
- if (Options.cls != 0)
+ if (keyn == '?')
{
- Options.cls = 0;
- printed = false;
+ // pick a job at random... see god retribution for proof this
+ // is uniform. -- bwr
+ int job_count = 0;
+ int job = -1;
+
+ for (int i = 0; i < NUM_JOBS; i++)
+ {
+ if (!you.species || class_allowed(you.species, i))
+ {
+ job_count++;
+ if (one_chance_in( job_count ))
+ job = i;
+ }
+ }
+
+ ASSERT( job != -1 ); // at least one class should have been allowed
+ you.char_class = job;
+
+ ng_cls = '?';
+ }
+ else if (keyn == '*')
+ {
+ pick_random_species_and_class();
+ // used to give random weapon/god as well
+ Options.random_pick = true;
+ ng_random = true;
+ return true;
+ }
+ else if ((keyn == ' ' && !you.species) ||
+ keyn == 'x' || keyn == ESCAPE || keyn == CK_BKSP)
+ {
+ you.char_class = JOB_UNKNOWN;
+ return false;
+ }
+ else if (keyn == 'X')
+ {
+ cprintf(EOL "Goodbye!");
+ end(0);
+ }
+ else
+ {
+ if (Options.cls != 0)
+ {
+ Options.cls = 0;
+ printed = false;
+ }
+ goto job_query;
}
- goto job_query;
}
- if (!class_allowed(you.species, you.char_class))
+ if (you.species && !class_allowed(you.species, you.char_class))
{
if (Options.cls != 0)
{
@@ -2926,7 +3261,10 @@ job_query:
goto job_query;
}
- return true;
+ if (ng_cls != '?')
+ ng_cls = keyn;
+
+ return you.char_class != JOB_UNKNOWN && you.species;
}
@@ -3207,9 +3545,12 @@ void give_items_skills()
you.skills[SK_INVOCATIONS] = 4;
if (Options.priest != GOD_NO_GOD && Options.priest != GOD_RANDOM)
- you.religion = Options.priest;
+ ng_pr = you.religion = Options.priest;
else if (Options.random_pick || Options.priest == GOD_RANDOM)
+ {
you.religion = coinflip() ? GOD_YREDELEMNUL : GOD_ZIN;
+ ng_pr = GOD_RANDOM;
+ }
else
{
clrscr();
@@ -3221,11 +3562,32 @@ void give_items_skills()
cprintf("a - Zin (for traditional priests)" EOL);
cprintf("b - Yredelemnul (for priests of death)" EOL);
+ if (Options.prev_pr != GOD_NO_GOD)
+ {
+ textcolor(BROWN);
+ cprintf(EOL "Enter - %s" EOL,
+ Options.prev_pr == GOD_ZIN? "Zin" :
+ Options.prev_pr == GOD_YREDELEMNUL? "Yredelemnul" :
+ "Random");
+ }
+
getkey:
keyn = get_ch();
+ if ((keyn == '\r' || keyn == '\n')
+ && Options.prev_pr != GOD_NO_GOD)
+ {
+ keyn = Options.prev_pr == GOD_ZIN? 'a' :
+ Options.prev_pr == GOD_YREDELEMNUL? 'b' :
+ '?';
+
+ }
+
switch (keyn)
{
+ case '?':
+ you.religion = coinflip()? GOD_ZIN : GOD_YREDELEMNUL;
+ break;
case 'a':
you.religion = GOD_ZIN;
break;
@@ -3235,6 +3597,8 @@ void give_items_skills()
default:
goto getkey;
}
+
+ ng_pr = keyn == '?'? GOD_RANDOM : you.religion;
}
break;
@@ -3514,7 +3878,7 @@ void give_items_skills()
you.inv[0].plus = 0;
you.inv[0].plus2 = 0;
you.inv[0].special = 0;
- you.inv[0].colour = LIGHTCYAN;
+ you.inv[0].colour = BROWN;
you.equip[EQ_WEAPON] = 0;
}
else if (you.species == SP_TROLL)
@@ -4138,10 +4502,13 @@ void give_items_skills()
else if (Options.death_knight != DK_NO_SELECTION
&& Options.death_knight != DK_RANDOM)
{
- choice = Options.death_knight;
+ ng_dk = choice = Options.death_knight;
}
else if (Options.random_pick || Options.death_knight == DK_RANDOM)
+ {
choice = (coinflip() ? DK_NECROMANCY : DK_YREDELEMNUL);
+ ng_dk = DK_RANDOM;
+ }
else
{
clrscr();
@@ -4153,11 +4520,31 @@ void give_items_skills()
cprintf("a - Necromantic magic" EOL);
cprintf("b - the god Yredelemnul" EOL);
+ if (Options.prev_dk != DK_NO_SELECTION)
+ {
+ textcolor(BROWN);
+ cprintf(EOL "Enter - %s" EOL,
+ Options.prev_dk == DK_NECROMANCY? "Necromancy" :
+ Options.prev_dk == DK_YREDELEMNUL? "Yredelemnul" :
+ "Random");
+ }
+
getkey1:
keyn = get_ch();
+ if ((keyn == '\r' || keyn == '\n')
+ && Options.prev_dk != DK_NO_SELECTION)
+ {
+ keyn = Options.prev_dk == DK_NECROMANCY? 'a' :
+ Options.prev_dk == DK_YREDELEMNUL? 'b' :
+ '?';
+ }
+
switch (keyn)
{
+ case '?':
+ choice = coinflip()? DK_NECROMANCY : DK_YREDELEMNUL;
+ break;
case 'a':
cprintf(EOL "Very well.");
choice = DK_NECROMANCY;
@@ -4168,6 +4555,8 @@ void give_items_skills()
default:
goto getkey1;
}
+
+ ng_dk = keyn == '?'? DK_RANDOM : choice;
}
switch (choice)
@@ -4232,10 +4621,13 @@ void give_items_skills()
if (Options.chaos_knight != GOD_NO_GOD
&& Options.chaos_knight != GOD_RANDOM)
{
- you.religion = Options.chaos_knight;
+ ng_ck = you.religion = Options.chaos_knight;
}
else if (Options.random_pick || Options.chaos_knight == GOD_RANDOM)
+ {
you.religion = coinflip() ? GOD_XOM : GOD_MAKHLEB;
+ ng_ck = GOD_RANDOM;
+ }
else
{
clrscr();
@@ -4247,12 +4639,33 @@ void give_items_skills()
cprintf("a - Xom of Chaos" EOL);
cprintf("b - Makhleb the Destroyer" EOL);
+ if (Options.prev_ck != GOD_NO_GOD)
+ {
+ textcolor(BROWN);
+ cprintf(EOL "Enter - %s" EOL,
+ Options.prev_ck == GOD_XOM? "Xom" :
+ Options.prev_ck == GOD_MAKHLEB? "Makhleb" :
+ "Random");
+ textcolor(LIGHTGREY);
+ }
+
getkey2:
keyn = get_ch();
+ if ((keyn == '\r' || keyn == '\n')
+ && Options.prev_ck != GOD_NO_GOD)
+ {
+ keyn = Options.prev_ck == GOD_XOM? 'a' :
+ Options.prev_ck == GOD_MAKHLEB? 'b' :
+ '?';
+ }
+
switch (keyn)
{
+ case '?':
+ you.religion = coinflip()? GOD_XOM : GOD_MAKHLEB;
+ break;
case 'a':
you.religion = GOD_XOM;
break;
@@ -4262,6 +4675,8 @@ void give_items_skills()
default:
goto getkey2;
}
+
+ ng_ck = keyn == '?'? GOD_RANDOM : you.religion;
}
if (you.religion == GOD_XOM)
diff --git a/trunk/source/ouch.cc b/trunk/source/ouch.cc
index cda5db8667..f9a5f514dd 100644
--- a/trunk/source/ouch.cc
+++ b/trunk/source/ouch.cc
@@ -27,6 +27,7 @@
#include "AppHdr.h"
#include <string.h>
+#include <string>
#include <stdio.h>
#include <stdlib.h>
#include <ctype.h>
@@ -57,6 +58,10 @@
#include "ouch.h"
+#ifdef __MINGW32__
+#include <io.h>
+#endif
+
#include "externs.h"
#include "chardump.h"
@@ -546,6 +551,9 @@ void ouch( int dam, int death_source, char death_type, const char *aux )
int d = 0;
int e = 0;
+ ait_hp_loss hpl(dam, death_type);
+ interrupt_activity( AI_HP_LOSS, &hpl );
+
if (you.deaths_door && death_type != KILLED_BY_LAVA
&& death_type != KILLED_BY_WATER)
{
@@ -625,7 +633,7 @@ void ouch( int dam, int death_source, char death_type, const char *aux )
mpr( info, MSGCH_DIAGNOSTICS );
#endif // DEBUG_DIAGNOSTICS
- if (!yesno("Die?", false))
+ if (!yesno("Die?", false, 'n'))
{
set_hp(you.hp_max, false);
return;
@@ -933,9 +941,20 @@ void end_game( struct scorefile_entry &se )
// last, but not least, delete player .sav file
strcpy(del_file, info);
+
+ std::string basefile = del_file;
+
strcat(del_file, ".sav");
unlink(del_file);
+ // Delete record of stashes, kills, travel cache and lua save.
+ unlink( (basefile + ".st").c_str() );
+ unlink( (basefile + ".kil").c_str() );
+ unlink( (basefile + ".tc").c_str() );
+#ifdef CLUA_BINDINGS
+ unlink( (basefile + ".lua").c_str() );
+#endif
+
// death message
if (dead)
{
@@ -956,7 +975,7 @@ void end_game( struct scorefile_entry &se )
}
}
- invent( -1, !dead );
+ invent( -1, dead );
clrscr();
if (!dump_char( "morgue.txt", !dead ))
diff --git a/trunk/source/output.cc b/trunk/source/output.cc
index 6b7568710b..415e970fc0 100644
--- a/trunk/source/output.cc
+++ b/trunk/source/output.cc
@@ -22,9 +22,14 @@
#include "externs.h"
+#include "fight.h"
#include "itemname.h"
+#include "menu.h"
#include "ouch.h"
#include "player.h"
+#include "religion.h"
+#include "skills2.h"
+#include "stuff.h"
static int bad_ench_colour( int lvl, int orange, int red )
{
@@ -271,7 +276,13 @@ void print_stats(void)
textcolor(you.inv[you.equip[EQ_WEAPON]].colour);
char str_pass[ ITEMNAME_SIZE ];
- in_name( you.equip[EQ_WEAPON], DESC_INVENTORY, str_pass, Options.terse_hand );
+ in_name( you.equip[EQ_WEAPON], DESC_INVENTORY, str_pass, false );
+ int prefcol = menu_colour(str_pass);
+ if (prefcol != -1)
+ textcolor(prefcol);
+
+ in_name( you.equip[EQ_WEAPON], DESC_INVENTORY, str_pass,
+ Options.terse_hand );
str_pass[39] = '\0';
cprintf(str_pass);
@@ -539,3 +550,269 @@ void print_stats(void)
update_screen();
#endif
} // end print_stats()
+
+unsigned char* itosym1(int stat)
+{
+ return (unsigned char*)( (stat >= 1) ? "+ " : ". " );
+}
+
+unsigned char* itosym3(int stat)
+{
+ return (unsigned char*)( (stat >= 3) ? "+ + +" :
+ (stat == 2) ? "+ + ." :
+ (stat == 1) ? "+ . ." :
+ (stat == 0) ? ". . ." :
+ "x . .");
+}
+
+static const char *s_equip_slot_names[] =
+{
+ "Weapon",
+ "Cloak",
+ "Helmet",
+ "Gloves",
+ "Boots",
+ "Shield",
+ "Armour",
+ "Left Ring",
+ "Right Ring",
+ "Amulet",
+};
+
+const char *equip_slot_to_name(int equip)
+{
+ if (equip == EQ_RINGS)
+ return "Ring";
+ if (equip < 0 || equip >= NUM_EQUIP)
+ return "";
+ return s_equip_slot_names[equip];
+}
+
+int equip_name_to_slot(const char *s)
+{
+ for (int i = 0; i < NUM_EQUIP; ++i)
+ {
+ if (!stricmp(s_equip_slot_names[i], s))
+ return i;
+ }
+ return -1;
+}
+
+void get_full_detail(char* buffer, bool calc_unid)
+{
+#define FIR_AD buffer,44
+#define CUR_AD &buffer[++lines*45],44
+#define BUF_SIZE 25*3*45
+ int lines = 0;
+
+ memset(buffer, 0, BUF_SIZE);
+
+ snprintf(CUR_AD, "%s the %s", you.your_name, player_title());
+ lines++;
+ snprintf(CUR_AD, "Race : %s", species_name(you.species,you.experience_level) );
+ snprintf(CUR_AD, "Class : %s", you.class_name);
+ snprintf(CUR_AD, "Worship : %s",
+ you.religion == GOD_NO_GOD? "" : god_name(you.religion) );
+ snprintf(CUR_AD, "Level : %7d", you.experience_level);
+ snprintf(CUR_AD, "Exp : %7lu", you.experience);
+
+ if (you.experience_level < 27)
+ {
+ int xp_needed = (exp_needed(you.experience_level+2) - you.experience) + 1;
+ snprintf(CUR_AD, "Next Level : %7lu", exp_needed(you.experience_level + 2) + 1);
+ snprintf(CUR_AD, "Exp Needed : %7d", xp_needed);
+ }
+ else
+ {
+ snprintf(CUR_AD, " ");
+ snprintf(CUR_AD, " ");
+ }
+
+ snprintf(CUR_AD, "Spls.Left : %7d", player_spell_levels() );
+ snprintf(CUR_AD, "Gold : %7d", you.gold );
+
+ lines++;
+
+ if (!player_rotted())
+ {
+ if (you.hp < you.hp_max)
+ snprintf(CUR_AD, "HP : %3d/%d", you.hp, you.hp_max);
+ else
+ snprintf(CUR_AD, "HP : %3d", you.hp);
+ }
+ else
+ {
+ snprintf(CUR_AD, "HP : %3d/%d (%d)",
+ you.hp, you.hp_max, you.hp_max + player_rotted() );
+ }
+
+ if (you.magic_points < you.max_magic_points)
+ snprintf(CUR_AD, "MP : %3d/%d",
+ you.magic_points, you.max_magic_points);
+ else
+ snprintf(CUR_AD, "MP : %3d", you.magic_points);
+
+ if (you.strength == you.max_strength)
+ snprintf(CUR_AD, "Str : %3d", you.strength);
+ else
+ snprintf(CUR_AD, "Str : %3d (%d)",
+ you.strength, you.max_strength);
+
+ if (you.intel == you.max_intel)
+ snprintf(CUR_AD, "Int : %3d", you.intel);
+ else
+ snprintf(CUR_AD, "Int : %3d (%d)", you.intel, you.max_intel);
+
+ if (you.dex == you.max_dex)
+ snprintf(CUR_AD, "Dex : %3d", you.dex);
+ else
+ snprintf(CUR_AD, "Dex : %3d (%d)", you.dex, you.max_dex);
+
+ snprintf(CUR_AD, "AC : %3d", player_AC() );
+ snprintf(CUR_AD, "Evasion : %3d", player_evasion() );
+ snprintf(CUR_AD, "Shield : %3d", player_shield_class() );
+ lines++;
+
+ if (you.real_time != -1)
+ {
+ const time_t curr = you.real_time + (time(NULL) - you.start_time);
+ char buff[200];
+ make_time_string( curr, buff, sizeof(buff), true );
+
+ snprintf(CUR_AD, "Play time : %10s", buff);
+ snprintf(CUR_AD, "Turns : %10ld", you.num_turns );
+ }
+
+ lines = 27;
+
+ snprintf(CUR_AD, "Res.Fire : %s",
+ itosym3( player_res_fire(calc_unid) ) );
+ snprintf(CUR_AD, "Res.Cold : %s",
+ itosym3( player_res_cold(calc_unid) ) );
+ snprintf(CUR_AD, "Life Prot.: %s",
+ itosym3( player_prot_life(calc_unid) ) );
+ snprintf(CUR_AD, "Res.Poison: %s",
+ itosym1( player_res_poison(calc_unid) ) );
+ snprintf(CUR_AD, "Res.Elec. : %s",
+ itosym1( player_res_electricity(calc_unid) ) );
+ lines++;
+
+ snprintf(CUR_AD, "Sust.Abil.: %s",
+ itosym1( player_sust_abil(calc_unid) ) );
+ snprintf(CUR_AD, "Res.Mut. : %s",
+ itosym1( wearing_amulet( AMU_RESIST_MUTATION, calc_unid) ) );
+ snprintf(CUR_AD, "Res.Slow : %s",
+ itosym1( wearing_amulet( AMU_RESIST_SLOW, calc_unid) ) );
+ snprintf(CUR_AD, "Clarity : %s",
+ itosym1( wearing_amulet( AMU_CLARITY, calc_unid) ) );
+ lines++;
+ lines++;
+
+ {
+ char str_pass[ITEMNAME_SIZE];
+ const int e_order[] =
+ {
+ EQ_WEAPON, EQ_BODY_ARMOUR, EQ_SHIELD, EQ_HELMET, EQ_CLOAK,
+ EQ_GLOVES, EQ_BOOTS, EQ_AMULET, EQ_RIGHT_RING, EQ_LEFT_RING
+ };
+
+ for(int i = 0; i < NUM_EQUIP; i++)
+ {
+ int eqslot = e_order[i];
+ const char *slot = equip_slot_to_name( eqslot );
+ if (eqslot == EQ_LEFT_RING || eqslot == EQ_RIGHT_RING)
+ slot = "Ring";
+ else if (eqslot == EQ_BOOTS
+ && (you.species == SP_CENTAUR
+ || you.species == SP_NAGA))
+ slot = "Barding";
+
+ if ( you.equip[ e_order[i] ] != -1)
+ {
+ in_name( you.equip[ e_order[i] ], DESC_PLAIN,
+ str_pass, Options.terse_hand );
+ snprintf(CUR_AD, "%-7s: %s", slot, str_pass);
+ }
+ else
+ {
+ if (e_order[i] == EQ_WEAPON)
+ {
+ if (you.attribute[ATTR_TRANSFORMATION] == TRAN_BLADE_HANDS)
+ snprintf(CUR_AD, "%-7s: Blade Hands", slot);
+ else if (you.skills[SK_UNARMED_COMBAT])
+ snprintf(CUR_AD, "%-7s: Unarmed", slot);
+ else
+ snprintf(CUR_AD, "%-7s:", slot);
+ }
+ else
+ {
+ snprintf(CUR_AD, "%-7s:", slot);
+ }
+ }
+ }
+ }
+
+ lines = 52;
+ snprintf(CUR_AD, "See Invis. : %s",
+ itosym1( player_see_invis(calc_unid) ) );
+ snprintf(CUR_AD, "Warding : %s",
+ itosym1( wearing_amulet(AMU_WARDING, calc_unid)
+ || (you.religion == GOD_VEHUMET &&
+ you.duration[DUR_PRAYER] &&
+ !player_under_penance() &&
+ you.piety >= 75) ) );
+ snprintf(CUR_AD, "Conserve : %s",
+ itosym1( wearing_amulet( AMU_CONSERVATION, calc_unid) ) );
+ snprintf(CUR_AD, "Res.Corr. : %s",
+ itosym1( wearing_amulet( AMU_RESIST_CORROSION, calc_unid) ) );
+
+ if ( !wearing_amulet( AMU_THE_GOURMAND, calc_unid) )
+ {
+ switch (you.species)
+ {
+ case SP_GHOUL:
+ snprintf(CUR_AD, "Saprovore : %s", itosym3(3) );
+ break;
+
+ case SP_KOBOLD:
+ case SP_TROLL:
+ snprintf(CUR_AD, "Saprovore : %s", itosym3(2) );
+ break;
+
+ case SP_HILL_ORC:
+ case SP_OGRE:
+ snprintf(CUR_AD, "Saprovore : %s", itosym3(1) );
+ break;
+#ifdef V_FIX
+ case SP_OGRE_MAGE:
+ snprintf(CUR_AD, "Voracious : %s", itosym1(1) );
+ break;
+#endif
+ default:
+ snprintf(CUR_AD, "Gourmand : %s", itosym1(0) );
+ break;
+ }
+ }
+ else
+ {
+ snprintf(CUR_AD, "Gourmand : %s",
+ itosym1( wearing_amulet( AMU_THE_GOURMAND, calc_unid) ) );
+ }
+
+ lines++;
+
+ if ( scan_randarts(RAP_PREVENT_TELEPORTATION, calc_unid) )
+ snprintf(CUR_AD, "Prev.Telep.: %s",
+ itosym1( scan_randarts(RAP_PREVENT_TELEPORTATION, calc_unid) ) );
+ else
+ snprintf(CUR_AD, "Rnd.Telep. : %s",
+ itosym1( player_teleport(calc_unid) ) );
+ snprintf(CUR_AD, "Ctrl.Telep.: %s",
+ itosym1( you.attribute[ATTR_CONTROL_TELEPORT] ) );
+ snprintf(CUR_AD, "Levitation : %s", itosym1( player_is_levitating() ) );
+ snprintf(CUR_AD, "Ctrl.Flight: %s",
+ itosym1( wearing_amulet(AMU_CONTROLLED_FLIGHT, calc_unid) ) );
+ lines++;
+
+ return;
+}
diff --git a/trunk/source/output.h b/trunk/source/output.h
index ab8f7e3c2d..1fa77fd34b 100644
--- a/trunk/source/output.h
+++ b/trunk/source/output.h
@@ -18,5 +18,15 @@
* *********************************************************************** */
void print_stats(void);
+/* ***********************************************************************
+ * called from: chardump
+ * *********************************************************************** */
+void get_full_detail(char* buffer, bool calc_unid);
+
+const char *equip_slot_to_name(int equip);
+
+int equip_name_to_slot(const char *s);
+
+const char *equip_slot_to_name(int equip);
#endif
diff --git a/trunk/source/player.cc b/trunk/source/player.cc
index ed06fd4d18..c4d5d6c047 100644
--- a/trunk/source/player.cc
+++ b/trunk/source/player.cc
@@ -30,7 +30,9 @@
#include "externs.h"
+#include "clua.h"
#include "itemname.h"
+#include "items.h"
#include "macro.h"
#include "misc.h"
#include "mon-util.h"
@@ -42,6 +44,7 @@
#include "spl-util.h"
#include "spells4.h"
#include "stuff.h"
+#include "travel.h"
#include "view.h"
#include "wpn-misc.h"
@@ -177,7 +180,7 @@ bool player_genus(unsigned char which_genus, unsigned char species)
// Looks in equipment "slot" to see if there is an equiped "sub_type".
// Returns number of matches (in the case of rings, both are checked)
-int player_equip( int slot, int sub_type )
+int player_equip( int slot, int sub_type, bool calc_unid )
{
int ret = 0;
@@ -197,7 +200,9 @@ int player_equip( int slot, int sub_type )
// Like above, but must be magical stave.
if (you.equip[EQ_WEAPON] != -1
&& you.inv[you.equip[EQ_WEAPON]].base_type == OBJ_STAVES
- && you.inv[you.equip[EQ_WEAPON]].sub_type == sub_type)
+ && you.inv[you.equip[EQ_WEAPON]].sub_type == sub_type
+ && (calc_unid ||
+ item_ident(you.inv[you.equip[EQ_WEAPON]], ISFLAG_KNOW_TYPE)))
{
ret++;
}
@@ -205,13 +210,17 @@ int player_equip( int slot, int sub_type )
case EQ_RINGS:
if (you.equip[EQ_LEFT_RING] != -1
- && you.inv[you.equip[EQ_LEFT_RING]].sub_type == sub_type)
+ && you.inv[you.equip[EQ_LEFT_RING]].sub_type == sub_type
+ && (calc_unid ||
+ item_type_known(you.inv[you.equip[EQ_LEFT_RING]])))
{
ret++;
}
if (you.equip[EQ_RIGHT_RING] != -1
- && you.inv[you.equip[EQ_RIGHT_RING]].sub_type == sub_type)
+ && you.inv[you.equip[EQ_RIGHT_RING]].sub_type == sub_type
+ && (calc_unid ||
+ item_type_known(you.inv[you.equip[EQ_RIGHT_RING]])))
{
ret++;
}
@@ -251,7 +260,9 @@ int player_equip( int slot, int sub_type )
default:
if (you.equip[slot] != -1
- && you.inv[you.equip[slot]].sub_type == sub_type)
+ && you.inv[you.equip[slot]].sub_type == sub_type
+ && (calc_unid ||
+ item_ident(you.inv[you.equip[slot]], ISFLAG_KNOW_TYPE)))
{
ret++;
}
@@ -376,12 +387,12 @@ int player_damage_brand( void )
return (ret);
}
-int player_teleport(void)
+int player_teleport(bool calc_unid)
{
int tp = 0;
/* rings */
- tp += 8 * player_equip( EQ_RINGS, RING_TELEPORTATION );
+ tp += 8 * player_equip( EQ_RINGS, RING_TELEPORTATION, calc_unid );
/* mutations */
tp += you.mutation[MUT_TELEPORT] * 3;
@@ -391,7 +402,7 @@ int player_teleport(void)
&& you.inv[you.equip[EQ_WEAPON]].base_type == OBJ_WEAPONS
&& is_random_artefact( you.inv[you.equip[EQ_WEAPON]] ))
{
- tp += scan_randarts(RAP_CAUSE_TELEPORTATION);
+ tp += scan_randarts(RAP_CAUSE_TELEPORTATION, calc_unid);
}
return tp;
@@ -598,19 +609,19 @@ int player_res_magic(void)
return rm;
}
-int player_res_fire(void)
+int player_res_fire(bool calc_unid)
{
int rf = 0;
/* rings of fire resistance/fire */
- rf += player_equip( EQ_RINGS, RING_PROTECTION_FROM_FIRE );
- rf += player_equip( EQ_RINGS, RING_FIRE );
+ rf += player_equip( EQ_RINGS, RING_PROTECTION_FROM_FIRE, calc_unid );
+ rf += player_equip( EQ_RINGS, RING_FIRE, calc_unid );
/* rings of ice */
- rf -= player_equip( EQ_RINGS, RING_ICE );
+ rf -= player_equip( EQ_RINGS, RING_ICE, calc_unid );
/* Staves */
- rf += player_equip( EQ_STAFF, STAFF_FIRE );
+ rf += player_equip( EQ_STAFF, STAFF_FIRE, calc_unid );
// body armour:
rf += 2 * player_equip( EQ_BODY_ARMOUR, ARM_DRAGON_ARMOUR );
@@ -622,7 +633,7 @@ int player_res_fire(void)
rf += player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_RESISTANCE );
// randart weapons:
- rf += scan_randarts(RAP_FIRE);
+ rf += scan_randarts(RAP_FIRE, calc_unid);
// species:
if (you.species == SP_MUMMY)
@@ -661,19 +672,19 @@ int player_res_fire(void)
return (rf);
}
-int player_res_cold(void)
+int player_res_cold(bool calc_unid)
{
int rc = 0;
/* rings of fire resistance/fire */
- rc += player_equip( EQ_RINGS, RING_PROTECTION_FROM_COLD );
- rc += player_equip( EQ_RINGS, RING_ICE );
+ rc += player_equip( EQ_RINGS, RING_PROTECTION_FROM_COLD, calc_unid );
+ rc += player_equip( EQ_RINGS, RING_ICE, calc_unid );
/* rings of ice */
- rc -= player_equip( EQ_RINGS, RING_FIRE );
+ rc -= player_equip( EQ_RINGS, RING_FIRE, calc_unid );
/* Staves */
- rc += player_equip( EQ_STAFF, STAFF_COLD );
+ rc += player_equip( EQ_STAFF, STAFF_COLD, calc_unid );
// body armour:
rc += 2 * player_equip( EQ_BODY_ARMOUR, ARM_ICE_DRAGON_ARMOUR );
@@ -685,7 +696,7 @@ int player_res_cold(void)
rc += player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_RESISTANCE );
// randart weapons:
- rc += scan_randarts(RAP_COLD);
+ rc += scan_randarts(RAP_COLD, calc_unid);
// species:
if (you.species == SP_MUMMY || you.species == SP_GHOUL)
@@ -724,7 +735,7 @@ int player_res_cold(void)
return (rc);
}
-int player_res_electricity(void)
+int player_res_electricity(bool calc_unid)
{
int re = 0;
@@ -732,13 +743,13 @@ int player_res_electricity(void)
re++;
// staff
- re += player_equip( EQ_STAFF, STAFF_AIR );
+ re += player_equip( EQ_STAFF, STAFF_AIR, calc_unid );
// body armour:
re += player_equip( EQ_BODY_ARMOUR, ARM_STORM_DRAGON_ARMOUR );
// randart weapons:
- re += scan_randarts(RAP_ELECTRICITY);
+ re += scan_randarts(RAP_ELECTRICITY, calc_unid);
// species:
if (you.species == SP_BLACK_DRACONIAN && you.experience_level > 17)
@@ -764,15 +775,15 @@ int player_res_electricity(void)
} // end player_res_electricity()
// funny that no races are susceptible to poisons {dlb}
-int player_res_poison(void)
+int player_res_poison(bool calc_unid)
{
int rp = 0;
/* rings of poison resistance */
- rp += player_equip( EQ_RINGS, RING_POISON_RESISTANCE );
+ rp += player_equip( EQ_RINGS, RING_POISON_RESISTANCE, calc_unid );
/* Staves */
- rp += player_equip( EQ_STAFF, STAFF_POISON );
+ rp += player_equip( EQ_STAFF, STAFF_POISON, calc_unid );
/* the staff of Olgreb: */
if (you.equip[EQ_WEAPON] != -1
@@ -794,7 +805,7 @@ int player_res_poison(void)
rp++;
// randart weapons:
- rp += scan_randarts(RAP_POISON);
+ rp += scan_randarts(RAP_POISON, calc_unid);
// species:
if (you.species == SP_MUMMY || you.species == SP_NAGA
@@ -984,12 +995,12 @@ unsigned char player_energy(void)
return pe;
}
-int player_prot_life(void)
+int player_prot_life(bool calc_unid)
{
int pl = 0;
// rings
- pl += player_equip( EQ_RINGS, RING_LIFE_PROTECTION );
+ pl += player_equip( EQ_RINGS, RING_LIFE_PROTECTION, calc_unid );
// armour: (checks body armour only)
pl += player_equip_ego_type( EQ_ALL_ARMOUR, SPARM_POSITIVE_ENERGY );
@@ -1016,7 +1027,7 @@ int player_prot_life(void)
}
// randart wpns
- pl += scan_randarts(RAP_NEGATIVE_ENERGY);
+ pl += scan_randarts(RAP_NEGATIVE_ENERGY, calc_unid);
// demonic power
pl += you.mutation[MUT_NEGATIVE_ENERGY_RESISTANCE];
@@ -1576,11 +1587,11 @@ int player_shield_class(void) //jmf: changes for new spell
return (base_shield);
} // end player_shield_class()
-unsigned char player_see_invis(void)
+unsigned char player_see_invis(bool calc_unid)
{
unsigned char si = 0;
- si += player_equip( EQ_RINGS, RING_SEE_INVISIBLE );
+ si += player_equip( EQ_RINGS, RING_SEE_INVISIBLE, calc_unid );
/* armour: (checks head armour only) */
si += player_equip_ego_type( EQ_HELMET, SPARM_SEE_INVISIBLE );
@@ -1597,7 +1608,7 @@ unsigned char player_see_invis(void)
si++;
/* randart wpns */
- int artefacts = scan_randarts(RAP_EYESIGHT);
+ int artefacts = scan_randarts(RAP_EYESIGHT, calc_unid);
if (artefacts > 0)
si += artefacts;
@@ -1619,11 +1630,11 @@ bool player_monster_visible( struct monsters *mon )
return (true);
}
-unsigned char player_sust_abil(void)
+unsigned char player_sust_abil(bool calc_unid)
{
unsigned char sa = 0;
- sa += player_equip( EQ_RINGS, RING_SUSTAIN_ABILITIES );
+ sa += player_equip( EQ_RINGS, RING_SUSTAIN_ABILITIES, calc_unid );
return sa;
} // end player_sust_abil()
@@ -1692,6 +1703,11 @@ int burden_change(void)
mpr("You are being crushed by all of your possessions.");
}
+ // Stop travel if we get burdened (as from potions of might/levitation
+ // wearing off).
+ if (you.burden_state > old_burdenstate)
+ interrupt_activity( AI_BURDEN_CHANGE );
+
return you.burden;
} // end burden_change()
@@ -2875,7 +2891,7 @@ char *species_name( int speci, int level, bool genus, bool adj, bool cap )
return (species_buff);
} // end species_name()
-bool wearing_amulet(char amulet)
+bool wearing_amulet(char amulet, bool calc_unid)
{
if (amulet == AMU_CONTROLLED_FLIGHT
&& (you.duration[DUR_CONTROLLED_FLIGHT]
@@ -2898,7 +2914,9 @@ bool wearing_amulet(char amulet)
if (you.equip[EQ_AMULET] == -1)
return false;
- if (you.inv[you.equip[EQ_AMULET]].sub_type == amulet)
+ if (you.inv[you.equip[EQ_AMULET]].sub_type == amulet
+ && (calc_unid ||
+ item_type_known(you.inv[you.equip[EQ_AMULET]])))
return true;
return false;
@@ -3096,7 +3114,7 @@ int slaying_bonus(char which_affected)
/* Checks each equip slot for a randart, and adds up all of those with
a given property. Slow if any randarts are worn, so avoid where possible. */
-int scan_randarts(char which_property)
+int scan_randarts(char which_property, bool calc_unid)
{
int i = 0;
int retval = 0;
@@ -3115,6 +3133,11 @@ int scan_randarts(char which_property)
if (!is_random_artefact( you.inv[ eq ] ))
continue;
+ // Ignore unidentified items [TileCrawl dump enhancements].
+ if (!item_ident(you.inv[ eq ], ISFLAG_KNOW_PROPERTIES) &&
+ !calc_unid)
+ continue;
+
retval += randart_wpn_property( you.inv[ eq ], which_property );
}
@@ -3131,6 +3154,10 @@ void modify_stat(unsigned char which_stat, char amount, bool suppress_msg)
if (amount == 0)
return;
+ // Stop running/travel if a stat drops.
+ if (amount < 0)
+ interrupt_activity( AI_STAT_CHANGE );
+
if (!suppress_msg)
strcpy(info, "You feel ");
@@ -3883,3 +3910,162 @@ void rot_player( int amount )
you.rotting += amount;
}
}
+
+void run_macro(const char *macroname)
+{
+ if (you.activity != ACT_NONE && you.activity != ACT_MACRO)
+ return;
+
+#ifdef CLUA_BINDINGS
+ if (!clua)
+ {
+ mpr("Lua not initialized", MSGCH_DIAGNOSTICS);
+ you.activity = ACT_NONE;
+ return;
+ }
+
+ if (!clua.callbooleanfn(false, "c_macro", "s", macroname))
+ {
+ if (clua.error.length())
+ mpr(clua.error.c_str());
+ you.activity = ACT_NONE;
+ }
+ else
+ {
+ you.activity = ACT_MACRO;
+ }
+#else
+ you.activity = ACT_NONE;
+#endif
+}
+
+void perform_activity()
+{
+ switch (you.activity)
+ {
+ case ACT_MULTIDROP:
+ drop();
+ break;
+ case ACT_MACRO:
+ run_macro();
+ break;
+ default:
+ break;
+ }
+}
+
+#ifdef CLUA_BINDINGS
+static const char *activity_interrupt_name(ACT_INTERRUPT ai)
+{
+ switch (ai)
+ {
+ case AI_FORCE_INTERRUPT: return "force";
+ case AI_KEYPRESS: return "keypress";
+ case AI_FULL_HP: return "full_hp";
+ case AI_FULL_MP: return "full_mp";
+ case AI_STATUE: return "statue";
+ case AI_HUNGRY: return "hungry";
+ case AI_MESSAGE: return "message";
+ case AI_HP_LOSS: return "hp_loss";
+ case AI_BURDEN_CHANGE: return "burden";
+ case AI_STAT_CHANGE: return "stat";
+ case AI_SEE_MONSTER: return "monster";
+ case AI_TELEPORT: return "teleport";
+ default: return "unknown";
+ }
+}
+
+static const char *activity_names[] = {
+ "",
+ "multidrop",
+ "run",
+ "travel",
+ "macro"
+};
+
+static const char *activity_name(int act)
+{
+ if (act < ACT_NONE || act >= ACT_ACTIVITY_COUNT)
+ return NULL;
+ return activity_names[act];
+}
+#endif
+
+static void kill_activity()
+{
+ if (you.running)
+ stop_running();
+ you.activity = ACT_NONE;
+}
+
+static bool userdef_interrupt_activity( ACT_INTERRUPT ai,
+ const activity_interrupt_t &at )
+{
+#ifdef CLUA_BINDINGS
+ lua_State *ls = clua.state();
+ if (!ls || ai == AI_FORCE_INTERRUPT)
+ {
+ if (ai == AI_FORCE_INTERRUPT || you.activity == ACT_MACRO)
+ kill_activity();
+ return true;
+ }
+
+ const char *interrupt_name = activity_interrupt_name(ai);
+ const char *act_name = activity_name(you.activity);
+
+ bool ran = clua.callfn("c_interrupt_activity", "1:ssA",
+ act_name, interrupt_name, &at);
+ if (ran)
+ {
+ // If the function returned nil, we want to cease processing.
+ if (lua_isnil(ls, -1))
+ {
+ lua_pop(ls, 1);
+ return false;
+ }
+
+ bool stopact = lua_toboolean(ls, -1);
+ lua_pop(ls, 1);
+ if (stopact)
+ {
+ kill_activity();
+ return true;
+ }
+ }
+
+ if (you.activity == ACT_MACRO &&
+ clua.callbooleanfn(true, "c_interrupt_macro",
+ "sA", interrupt_name, &at))
+ {
+ kill_activity();
+ }
+
+#else
+ if (you.activity == ACT_MACRO)
+ kill_activity();
+#endif
+ return true;
+}
+
+void interrupt_activity( ACT_INTERRUPT ai, const activity_interrupt_t &at )
+{
+ if (you.running && !you.activity)
+ you.activity = you.running > 0? ACT_RUNNING : ACT_TRAVELING;
+
+ if (!you.activity)
+ return;
+
+ if (!userdef_interrupt_activity(ai, at) || !you.activity)
+ {
+ if (you.activity == ACT_RUNNING || you.activity == ACT_TRAVELING)
+ you.activity = ACT_NONE;
+ return;
+ }
+
+ if (!ai || (Options.activity_interrupts[ you.activity ] & ai)) {
+ kill_activity();
+ }
+
+ if (you.activity == ACT_RUNNING || you.activity == ACT_TRAVELING)
+ you.activity = ACT_NONE;
+}
diff --git a/trunk/source/player.h b/trunk/source/player.h
index fc99a8b1be..1d99d6b154 100644
--- a/trunk/source/player.h
+++ b/trunk/source/player.h
@@ -17,7 +17,7 @@
bool player_in_branch( int branch );
bool player_in_hell( void );
-int player_equip( int slot, int sub_type );
+int player_equip( int slot, int sub_type, bool calc_unid = true );
int player_equip_ego_type( int slot, int sub_type );
int player_damage_type( void );
int player_damage_brand( void );
@@ -53,7 +53,7 @@ bool player_under_penance(void);
* called from: ability - acr - fight - food - it_use2 - item_use - items -
* misc - mutation - ouch
* *********************************************************************** */
-bool wearing_amulet(char which_am);
+bool wearing_amulet(char which_am, bool calc_unid = true);
/* ***********************************************************************
@@ -134,7 +134,7 @@ int player_magical_power( void );
/* ***********************************************************************
* called from: fight - misc - ouch - spells
* *********************************************************************** */
-int player_prot_life(void);
+int player_prot_life(bool calc_unid = true);
/* ***********************************************************************
@@ -146,26 +146,26 @@ int player_regen(void);
/* ***********************************************************************
* called from: fight - files - it_use2 - misc - ouch - spells - spells2
* *********************************************************************** */
-int player_res_cold(void);
+int player_res_cold(bool calc_unid = true);
/* ***********************************************************************
* called from: fight - files - ouch
* *********************************************************************** */
-int player_res_electricity(void);
+int player_res_electricity(bool calc_unid = true);
/* ***********************************************************************
* called from: acr - fight - misc - ouch - spells
* *********************************************************************** */
-int player_res_fire(void);
+int player_res_fire(bool calc_unid = true);
/* ***********************************************************************
* called from: beam - decks - fight - fod - it_use2 - misc - ouch -
* spells - spells2
* *********************************************************************** */
-int player_res_poison(void);
+int player_res_poison(bool calc_unid = true);
int player_res_magic(void);
@@ -251,19 +251,19 @@ int player_spell_levels(void);
/* ***********************************************************************
* called from: effects
* *********************************************************************** */
-unsigned char player_sust_abil(void);
+unsigned char player_sust_abil(bool calc_unid = true);
/* ***********************************************************************
* called from: acr
* *********************************************************************** */
-int player_teleport(void);
+int player_teleport(bool calc_unid = true);
/* ***********************************************************************
* called from: ability - acr - items - misc - spells1 - spells3
* *********************************************************************** */
-int scan_randarts(char which_property);
+int scan_randarts(char which_property, bool calc_unid = true);
/* ***********************************************************************
@@ -277,7 +277,7 @@ int slaying_bonus(char which_affected);
* items - monstuff - mon-util - mstuff2 - spells1 - spells2 -
* spells3
* *********************************************************************** */
-unsigned char player_see_invis(void);
+unsigned char player_see_invis(bool calc_unid = true);
bool player_monster_visible( struct monsters *mon );
@@ -436,11 +436,17 @@ void dec_disease_player();
void rot_player( int amount );
+void perform_activity();
+
+void interrupt_activity( ACT_INTERRUPT ai,
+ const activity_interrupt_t &a = activity_interrupt_t() );
+
// last updated 15sep2001 {bwr}
/* ***********************************************************************
* called from:
* *********************************************************************** */
bool player_has_spell( int spell );
+void run_macro(const char *macroname = NULL);
#endif
diff --git a/trunk/source/randart.cc b/trunk/source/randart.cc
index 939cdfecaa..b0486fe02f 100644
--- a/trunk/source/randart.cc
+++ b/trunk/source/randart.cc
@@ -611,8 +611,10 @@ const char *rand_armour_names[] = {
#if defined(MAC) || defined(__IBMCPP__) || defined(__BCPLUSPLUS__)
#define PACKED
#else
+#ifndef PACKED
#define PACKED __attribute__ ((packed))
#endif
+#endif
//int unranddatasize;
@@ -652,18 +654,12 @@ static struct unrandart_entry unranddata[] = {
char *art_n;
static FixedVector < char, NO_UNRANDARTS > unrandart_exist;
-static int random5( int randmax );
+// static int random5( int randmax );
static struct unrandart_entry *seekunrandart( const item_def &item );
-static int random5( int randmax )
+static inline int random5( int randmax )
{
- if (randmax <= 0)
- return (0);
-
- //return rand() % randmax;
- return ((int) rand() / (RAND_MAX / randmax + 1));
- // must use random (not rand) for the predictable-results-from-known
- // -srandom-seeds thing to work.
+ return random2(randmax);
}
void set_unrandart_exist(int whun, char is_exist)
@@ -769,8 +765,8 @@ void randart_wpn_properties( const item_def &item,
// long seed = aclass * adam + atype * (aplus % 100) + aplus2 * 100;
long seed = calc_seed( item );
- long randstore = rand();
- srand( seed );
+ push_rng_state();
+ seed_rng( seed );
if (aclass == OBJ_ARMOUR)
power_level = item.plus / 2 + 2;
@@ -1205,7 +1201,7 @@ finished_curses:
if ((power_level < 2 && random5(5) == 0) || random5(30) == 0)
proprt[RAP_CURSED] = 1;
- srand(randstore);
+ pop_rng_state();
}
@@ -1240,8 +1236,8 @@ const char *randart_name( const item_def &item )
// long seed = aclass + adam * (aplus % 100) + atype * aplus2;
long seed = calc_seed( item );
- long randstore = rand();
- srand( seed );
+ push_rng_state();
+ seed_rng( seed );
if (item_not_ident( item, ISFLAG_KNOW_TYPE ))
{
@@ -1274,7 +1270,7 @@ const char *randart_name( const item_def &item )
standard_name_weap( item.sub_type, st_p3 );
strcat(art_n, st_p3);
- srand(randstore);
+ pop_rng_state();
return (art_n);
}
@@ -1307,7 +1303,7 @@ const char *randart_name( const item_def &item )
}
}
- srand(randstore);
+ pop_rng_state();
return (art_n);
}
@@ -1336,8 +1332,9 @@ const char *randart_armour_name( const item_def &item )
// long seed = aclass + adam * (aplus % 100) + atype * aplus2;
long seed = calc_seed( item );
- long randstore = rand();
- srand( seed );
+
+ push_rng_state();
+ seed_rng( seed );
if (item_not_ident( item, ISFLAG_KNOW_TYPE ))
{
@@ -1369,7 +1366,7 @@ const char *randart_armour_name( const item_def &item )
standard_name_armour(item, st_p3);
strcat(art_n, st_p3);
- srand(randstore);
+ pop_rng_state();
return (art_n);
}
@@ -1401,7 +1398,7 @@ const char *randart_armour_name( const item_def &item )
}
}
- srand(randstore);
+ pop_rng_state();
return (art_n);
}
@@ -1432,8 +1429,8 @@ const char *randart_ring_name( const item_def &item )
// long seed = aclass + adam * (aplus % 100) + atype * aplus2;
long seed = calc_seed( item );
- long randstore = rand();
- srand( seed );
+ push_rng_state();
+ seed_rng( seed );
if (item_not_ident( item, ISFLAG_KNOW_TYPE ))
{
@@ -1464,7 +1461,7 @@ const char *randart_ring_name( const item_def &item )
strcat(art_n, " ");
strcat(art_n, (item.sub_type < AMU_RAGE) ? "ring" : "amulet");
- srand(randstore);
+ pop_rng_state();
return (art_n);
}
@@ -1493,7 +1490,7 @@ const char *randart_ring_name( const item_def &item )
}
}
- srand(randstore);
+ pop_rng_state();
return (art_n);
} // end randart_ring_name()
@@ -1720,7 +1717,7 @@ bool make_item_randart( item_def &item )
}
item.flags |= ISFLAG_RANDART;
- item.special = (random() & RANDART_SEED_MASK);
+ item.special = (random_int() & RANDART_SEED_MASK);
return (true);
}
diff --git a/trunk/source/religion.cc b/trunk/source/religion.cc
index 2871b9bd8f..fc51bc28bd 100644
--- a/trunk/source/religion.cc
+++ b/trunk/source/religion.cc
@@ -273,7 +273,8 @@ void pray(void)
if (thing_created != NON_ITEM)
{
move_item_to_grid( &thing_created, you.x_pos, you.y_pos );
-
+ origin_acquired(mitm[thing_created], you.religion);
+
simple_god_message(" grants you a gift!");
more();
canned_msg(MSG_SOMETHING_APPEARS);
@@ -293,11 +294,11 @@ void pray(void)
if (you.religion == GOD_TROG
|| (you.religion == GOD_OKAWARU && coinflip()))
{
- success = acquirement(OBJ_WEAPONS);
+ success = acquirement(OBJ_WEAPONS, you.religion);
}
else
{
- success = acquirement(OBJ_ARMOUR);
+ success = acquirement(OBJ_ARMOUR, you.religion);
}
if (success)
@@ -386,7 +387,7 @@ void pray(void)
&& grd[you.x_pos][you.y_pos] != DNGN_DEEP_WATER))
{
if (gift == OBJ_RANDOM)
- success = acquirement(OBJ_BOOKS);
+ success = acquirement(OBJ_BOOKS, you.religion);
else
{
int thing_created = items(1, OBJ_BOOKS, gift, true, 1, 250);
@@ -396,7 +397,10 @@ void pray(void)
move_item_to_grid( &thing_created, you.x_pos, you.y_pos );
if (thing_created != NON_ITEM)
+ {
success = true;
+ origin_acquired(mitm[thing_created], you.religion);
+ }
}
if (success)
@@ -834,6 +838,7 @@ void Xom_acts(bool niceness, int sever, bool force_sever)
if (thing_created != NON_ITEM)
{
+ origin_acquired(mitm[thing_created], GOD_XOM);
canned_msg(MSG_SOMETHING_APPEARS);
more();
}
@@ -870,7 +875,7 @@ void Xom_acts(bool niceness, int sever, bool force_sever)
(temp_rand == 2) ? "Xom grants you an implement of death."
: "Xom smiles on you.");
- if (acquirement(OBJ_WEAPONS))
+ if (acquirement(OBJ_WEAPONS, GOD_XOM))
more();
done_good = true;
@@ -1138,7 +1143,7 @@ void gain_piety(char pgn)
(you.religion == GOD_MAKHLEB)
? "gain power from killing in Makhleb's name" :
(you.religion == GOD_OKAWARU)
- ? "give your great, but temporary, body strength" :
+ ? "give your body great, but temporary strength" :
(you.religion == GOD_TROG)
? "go berserk at will" :
(you.religion == GOD_ELYVILON)
diff --git a/trunk/source/shopping.cc b/trunk/source/shopping.cc
index 8f3b3cf495..dee10a8d8c 100644
--- a/trunk/source/shopping.cc
+++ b/trunk/source/shopping.cc
@@ -11,6 +11,7 @@
*/
#include "AppHdr.h"
+#include "chardump.h"
#include "shopping.h"
#include <stdio.h>
@@ -31,24 +32,24 @@
#include "player.h"
#include "randart.h"
#include "spl-book.h"
+#include "stash.h"
#include "stuff.h"
-
-static char in_a_shop(char shoppy, char id[4][50]);
+static char in_a_shop(char shoppy, id_arr id);
static char more3(void);
static void purchase( int shop, int item_got, int cost );
-static void shop_init_id(int i, FixedArray < int, 4, 50 > &shop_id);
+static void shop_init_id(int i, id_fix_arr &shop_id);
static void shop_print(const char *shoppy, char sh_line);
-static void shop_set_ident_type(int i, FixedArray < int, 4, 50 > &shop_id,
+static void shop_set_ident_type(int i, id_fix_arr &shop_id,
unsigned char base_type, unsigned char sub_type);
-static void shop_uninit_id(int i, FixedArray < int, 4, 50 > &shop_id);
+static void shop_uninit_id(int i, const id_fix_arr &shop_id);
-char in_a_shop( char shoppy, char id[4][50] )
+char in_a_shop( char shoppy, id_arr id )
{
// easier to work with {dlb}
unsigned int greedy = env.shop[shoppy].greed;
- FixedArray < int, 4, 50 > shop_id;
+ id_fix_arr shop_id;
FixedVector < int, 20 > shop_items;
char st_pass[ ITEMNAME_SIZE ] = "";
@@ -71,6 +72,10 @@ char in_a_shop( char shoppy, char id[4][50] )
snprintf( info, INFO_SIZE, "Welcome to %s!",
shop_name(env.shop[shoppy].x, env.shop[shoppy].y) );
+#ifdef STASH_TRACKING
+ ShopInfo &si = stashes.get_shop(env.shop[shoppy].x, env.shop[shoppy].y);
+#endif
+
shop_print(info, 20);
more3();
@@ -89,6 +94,9 @@ char in_a_shop( char shoppy, char id[4][50] )
clrscr();
itty = igrd[0][5 + shoppy];
+#ifdef STASH_TRACKING
+ si.reset();
+#endif
if (itty == NON_ITEM)
{
shop_print("I'm sorry, my shop is empty now.", 20);
@@ -127,6 +135,15 @@ char in_a_shop( char shoppy, char id[4][50] )
if (gp_value <= 1)
gp_value = 1;
+ std::string desc;
+ if (is_dumpable_artifact(mitm[itty], Options.verbose_dump))
+ desc = munge_description(get_item_description(mitm[itty],
+ Options.verbose_dump,
+ true ));
+# ifdef STASH_TRACKING
+ si.add_item(mitm[itty], gp_value);
+# endif
+
gotoxy(60, i);
// cdl - itoa(gp_value, st_pass, 10);
snprintf(st_pass, sizeof(st_pass), "%5d", gp_value);
@@ -248,15 +265,13 @@ char in_a_shop( char shoppy, char id[4][50] )
return 0;
}
-void shop_init_id(int i, FixedArray < int, 4, 50 > &shop_id)
+void shop_init_id_type(int shoptype, id_fix_arr &shop_id)
{
- unsigned char j = 0;
-
- if (env.shop[i].type != SHOP_WEAPON_ANTIQUE
- && env.shop[i].type != SHOP_ARMOUR_ANTIQUE
- && env.shop[i].type != SHOP_GENERAL_ANTIQUE)
+ if (shoptype != SHOP_WEAPON_ANTIQUE
+ && shoptype != SHOP_ARMOUR_ANTIQUE
+ && shoptype != SHOP_GENERAL_ANTIQUE)
{
- for (j = 0; j < 50; j++)
+ for (int j = 0; j < 50; j++)
{
shop_id[ IDTYPE_WANDS ][j] = get_ident_type(OBJ_WANDS, j);
set_ident_type(OBJ_WANDS, j, ID_KNOWN_TYPE);
@@ -273,25 +288,37 @@ void shop_init_id(int i, FixedArray < int, 4, 50 > &shop_id)
}
}
-void shop_uninit_id(int i, FixedArray < int, 4, 50 > &shop_id)
+static void shop_init_id(int i, id_fix_arr &shop_id)
{
- unsigned char j = 0;
+ shop_init_id_type( env.shop[i].type, shop_id );
+}
- if (env.shop[i].type != SHOP_WEAPON_ANTIQUE
- && env.shop[i].type != SHOP_ARMOUR_ANTIQUE
- && env.shop[i].type != SHOP_GENERAL_ANTIQUE)
+void shop_uninit_id_type(int shoptype, const id_fix_arr &shop_id)
+{
+ if (shoptype != SHOP_WEAPON_ANTIQUE
+ && shoptype != SHOP_ARMOUR_ANTIQUE
+ && shoptype != SHOP_GENERAL_ANTIQUE)
{
- for (j = 0; j < 50; j++)
+ for (int j = 0; j < 50; j++)
{
- set_ident_type( OBJ_WANDS, j, shop_id[ IDTYPE_WANDS ][j], true );
- set_ident_type( OBJ_SCROLLS, j, shop_id[ IDTYPE_SCROLLS ][j], true );
- set_ident_type( OBJ_JEWELLERY, j, shop_id[ IDTYPE_JEWELLERY ][j], true );
- set_ident_type( OBJ_POTIONS, j, shop_id[ IDTYPE_POTIONS ][j], true );
+ set_ident_type( OBJ_WANDS, j,
+ shop_id[ IDTYPE_WANDS ][j], true );
+ set_ident_type( OBJ_SCROLLS, j,
+ shop_id[ IDTYPE_SCROLLS ][j], true );
+ set_ident_type( OBJ_JEWELLERY, j,
+ shop_id[ IDTYPE_JEWELLERY ][j], true );
+ set_ident_type( OBJ_POTIONS, j,
+ shop_id[ IDTYPE_POTIONS ][j], true );
}
}
}
-void shop_set_ident_type( int i, FixedArray < int, 4, 50 > &shop_id,
+static void shop_uninit_id(int i, const id_fix_arr &shop_id)
+{
+ shop_uninit_id_type(env.shop[i].type, shop_id);
+}
+
+void shop_set_ident_type( int i, id_fix_arr &shop_id,
unsigned char base_type, unsigned char sub_type )
{
if (env.shop[i].type != SHOP_WEAPON_ANTIQUE
@@ -343,6 +370,7 @@ static void purchase( int shop, int item_got, int cost )
{
you.gold -= cost;
+ origin_purchased(mitm[item_got]);
int num = move_item_to_player( item_got, mitm[item_got].quantity, true );
// Shopkeepers will now place goods you can't carry outside the shop.
@@ -462,7 +490,7 @@ int randart_value( const item_def &item )
return ((ret > 0) ? ret : 0);
}
-unsigned int item_value( item_def item, char id[4][50], bool ident )
+unsigned int item_value( item_def item, id_arr id, bool ident )
{
// Note that we pass item in by value, since we want a local
// copy to mangle as neccessary... maybe that should be fixed,
@@ -1517,7 +1545,7 @@ void shop(void)
return;
}
- char identy[4][50];
+ id_arr identy;
save_id(identy);
@@ -1528,38 +1556,45 @@ void shop(void)
redraw_screen();
} // end shop()
-const char *shop_name(int sx, int sy)
+const shop_struct *get_shop(int sx, int sy)
{
- static char sh_name[80];
- int shoppy;
-
- // paranoia
if (grd[sx][sy] != DNGN_ENTER_SHOP)
- return ("");
+ return (NULL);
// find shop
- for(shoppy = 0; shoppy < MAX_SHOPS; shoppy ++)
+ for (int shoppy = 0; shoppy < MAX_SHOPS; shoppy ++)
{
// find shop index plus a little bit of paranoia
if (env.shop[shoppy].x == sx && env.shop[shoppy].y == sy &&
env.shop[shoppy].type != SHOP_UNASSIGNED)
{
- break;
+ return (&env.shop[shoppy]);
}
}
+ return (NULL);
+}
+
+const char *shop_name(int sx, int sy)
+{
+ static char sh_name[80];
+ const shop_struct *cshop = get_shop(sx, sy);
+
+ // paranoia
+ if (grd[sx][sy] != DNGN_ENTER_SHOP)
+ return ("");
- if (shoppy == MAX_SHOPS)
+ if (!cshop)
{
mpr("Help! Non-existent shop.");
return ("Buggy Shop");
}
- int shop_type = env.shop[shoppy].type;
+ int shop_type = cshop->type;
char st_p[ITEMNAME_SIZE];
- make_name( env.shop[shoppy].keeper_name[0], env.shop[shoppy].keeper_name[1],
- env.shop[shoppy].keeper_name[2], 3, st_p );
+ make_name( cshop->keeper_name[0], cshop->keeper_name[1],
+ cshop->keeper_name[2], 3, st_p );
strcpy(sh_name, st_p);
strcat(sh_name, "'s ");
diff --git a/trunk/source/shopping.h b/trunk/source/shopping.h
index df6936deb0..8891f16eb9 100644
--- a/trunk/source/shopping.h
+++ b/trunk/source/shopping.h
@@ -14,6 +14,12 @@
#include "externs.h"
+typedef FixedArray < int, 4, 50 > id_fix_arr;
+typedef char id_arr[4][50];
+
+void shop_init_id_type(int shoptype, id_fix_arr &shop_id);
+void shop_uninit_id_type(int shoptype, const id_fix_arr &shop_id);
+
int randart_value( const item_def &item );
// last updated 12may2000 {dlb}
@@ -23,7 +29,7 @@ int randart_value( const item_def &item );
// ident == true overrides the item ident level and gives the price
// as if the item was fully id'd
-unsigned int item_value( item_def item, char id[4][50], bool ident = false );
+unsigned int item_value( item_def item, id_arr id, bool ident = false );
// last updated 12may2000 {dlb}
@@ -32,7 +38,7 @@ unsigned int item_value( item_def item, char id[4][50], bool ident = false );
* *********************************************************************** */
void shop(void);
-
+const shop_struct *get_shop(int sx, int sy);
// last updated 06mar2001 {gdl}
/* ***********************************************************************
diff --git a/trunk/source/skills.cc b/trunk/source/skills.cc
index af7d88f49a..9dc63d54f0 100644
--- a/trunk/source/skills.cc
+++ b/trunk/source/skills.cc
@@ -155,6 +155,9 @@ void exercise(char exsk, int deg)
if (!you.practise_skill[exsk] && !one_chance_in(4))
break;
+ if (you.skills[exsk] >= 27)
+ break;
+
exercise2( exsk );
deg--;
}
diff --git a/trunk/source/spells2.cc b/trunk/source/spells2.cc
index 102e2b7bc0..dbd8f00f3b 100644
--- a/trunk/source/spells2.cc
+++ b/trunk/source/spells2.cc
@@ -100,7 +100,11 @@ unsigned char detect_items( int pow )
if (igrd[i][j] != NON_ITEM)
{
- env.map[i - 1][j - 1] = '~';
+ unsigned short flags = env.map[i - 1][j - 1];
+ flags = !flags || (flags & ENVF_DETECTED)?
+ ENVF_DETECTED
+ : 0;
+ env.map[i - 1][j - 1] = '~' | ENVF_DETECT_ITEM | flags;
}
}
}
@@ -129,7 +133,13 @@ unsigned char detect_creatures( int pow )
{
struct monsters *mon = &menv[ mgrd[i][j] ];
- env.map[i - 1][j - 1] = mons_char( mon->type );
+ unsigned short flags = env.map[i - 1][j - 1];
+ flags = !flags || (flags & ENVF_DETECTED)?
+ ENVF_DETECTED
+ : 0;
+
+ env.map[i - 1][j - 1] =
+ mons_char( mon->type ) | ENVF_DETECT_MONS | flags;
// Assuming that highly intelligent spellcasters can
// detect scyring. -- bwr
@@ -156,7 +166,7 @@ int corpse_rot(int power)
char minx = you.x_pos - 6;
char maxx = you.x_pos + 7;
char miny = you.y_pos - 6;
- char maxy = you.y_pos + 6;
+ char maxy = you.y_pos + 7;
char xinc = 1;
char yinc = 1;
@@ -239,7 +249,7 @@ int animate_dead( int power, int corps_beh, int corps_hit, int actual )
int minx = you.x_pos - 6;
int maxx = you.x_pos + 7;
int miny = you.y_pos - 6;
- int maxy = you.y_pos + 6;
+ int maxy = you.y_pos + 7;
int xinc = 1;
int yinc = 1;
@@ -316,16 +326,23 @@ int animate_a_corpse( int axps, int ayps, int corps_beh, int corps_hit,
{
if (igrd[axps][ayps] == NON_ITEM)
return 0;
- else if (mitm[igrd[axps][ayps]].base_type != OBJ_CORPSES)
- return 0;
- else if (class_allowed == CORPSE_SKELETON
- && mitm[igrd[axps][ayps]].sub_type != CORPSE_SKELETON)
- return 0;
- else
- if (raise_corpse( igrd[axps][ayps], axps, ayps,
- corps_beh, corps_hit, 1 ) > 0)
+
+ int objl = igrd[axps][ayps];
+ // This searches all the items on the ground for a corpse
+ while (objl != NON_ITEM)
{
- mpr("The dead are walking!");
+ if (mitm[objl].base_type != OBJ_CORPSES
+ || (class_allowed == CORPSE_SKELETON
+ && mitm[objl].sub_type != CORPSE_SKELETON))
+ {
+ objl = mitm[objl].link;
+ continue;
+ }
+
+ if (raise_corpse(objl, axps, ayps,
+ corps_beh, corps_hit, 1 ) > 0)
+ mpr("The dead are walking!");
+ break;
}
return 0;
diff --git a/trunk/source/spells3.cc b/trunk/source/spells3.cc
index fd6ea6043e..e2a2bf325f 100644
--- a/trunk/source/spells3.cc
+++ b/trunk/source/spells3.cc
@@ -613,6 +613,8 @@ void you_teleport2( bool allow_control, bool new_abyss_area )
if (current_delay_action() == DELAY_BUTCHER)
stop_delay();
+ interrupt_activity( AI_TELEPORT );
+
if (you.duration[DUR_CONDENSATION_SHIELD] > 0)
{
you.duration[DUR_CONDENSATION_SHIELD] = 0;
@@ -1059,7 +1061,7 @@ void portal(void)
you.your_level = target_level - 1;
grd[you.x_pos][you.y_pos] = DNGN_STONE_STAIRS_DOWN_I;
- down_stairs( true, old_level );
+ down_stairs( true, old_level, true );
untag_followers();
}
diff --git a/trunk/source/spells4.cc b/trunk/source/spells4.cc
index 46ac36ebd0..26b09c3901 100644
--- a/trunk/source/spells4.cc
+++ b/trunk/source/spells4.cc
@@ -2056,7 +2056,7 @@ void cast_fulsome_distillation( int powc )
it_name( curr_item, DESC_NOCAP_THE, str_pass );
snprintf( info, INFO_SIZE, "Distill a potion from %s?", str_pass );
- if (yesno( info, true, false ))
+ if (yesno( info, true, 0, false ))
{
corpse = curr_item;
break;
diff --git a/trunk/source/spl-book.cc b/trunk/source/spl-book.cc
index bab93b9600..c6efbae9cf 100644
--- a/trunk/source/spl-book.cc
+++ b/trunk/source/spl-book.cc
@@ -764,11 +764,15 @@ int which_spell_in_book(int sbook_type, int spl)
return (wsib_pass[ spl + 1 ]);
} // end which_spell_in_book()
-static unsigned char spellbook_contents( item_def &book, int action )
+// If fs is not NULL, updates will be to the formatted_string instead of
+// the display.
+unsigned char spellbook_contents( item_def &book, int action,
+ formatted_string *fs )
{
FixedVector<int, SPELLBOOK_SIZE> spell_types; // was 10 {dlb}
int spelcount = 0;
int i, j;
+ bool update_screen = !fs;
const int spell_levels = player_spell_levels();
@@ -789,29 +793,23 @@ static unsigned char spellbook_contents( item_def &book, int action )
set_ident_flags( book, ISFLAG_KNOW_TYPE );
-#ifdef DOS_TERM
- char buffer[4800];
- gettext(1, 1, 80, 25, buffer);
- window(1, 1, 80, 25);
-#endif
-
spellbook_template( type, spell_types );
- clrscr();
- textcolor(LIGHTGREY);
+ formatted_string out;
+ out.textcolor(LIGHTGREY);
char str_pass[ ITEMNAME_SIZE ];
item_name( book, DESC_CAP_THE, str_pass );
- cprintf( str_pass );
+ out.cprintf( str_pass );
- cprintf( EOL EOL " Spells Type Level" EOL );
+ out.cprintf( EOL EOL " Spells Type Level" EOL );
for (j = 1; j < SPELLBOOK_SIZE; j++)
{
if (spell_types[j] == SPELL_NO_SPELL)
continue;
- cprintf(" ");
+ out.cprintf(" ");
bool knowsSpell = false;
for (i = 0; i < 25 && !knowsSpell; i++)
@@ -843,7 +841,7 @@ static unsigned char spellbook_contents( item_def &book, int action )
}
}
- textcolor( colour );
+ out.textcolor( colour );
// Old:
// textcolor(knowsSpell ? DARKGREY : LIGHTGREY);
@@ -854,15 +852,15 @@ static unsigned char spellbook_contents( item_def &book, int action )
strng[0] = index_to_letter(spelcount);
strng[1] = '\0';
- cprintf(strng);
- cprintf(" - ");
+ out.cprintf(strng);
+ out.cprintf(" - ");
- cprintf( spell_title(spell_types[j]) );
- gotoxy( 35, wherey() );
+ out.cprintf( spell_title(spell_types[j]) );
+ out.gotoxy( 35, -1 );
if (action == RBOOK_USE_STAFF)
- cprintf( "Evocations" );
+ out.cprintf( "Evocations" );
else
{
bool already = false;
@@ -872,55 +870,70 @@ static unsigned char spellbook_contents( item_def &book, int action )
if (spell_typematch( spell_types[j], 1 << i ))
{
if (already)
- cprintf( "/" );
+ out.cprintf( "/" );
- cprintf( spelltype_name( 1 << i ) );
+ out.cprintf( spelltype_name( 1 << i ) );
already = true;
}
}
}
- gotoxy( 65, wherey() );
+ out.gotoxy( 65, -1 );
char sval[3];
itoa( level_diff, sval, 10 );
- cprintf( sval );
- cprintf( EOL );
+ out.cprintf( sval );
+ out.cprintf( EOL );
spelcount++;
}
- textcolor(LIGHTGREY);
- cprintf(EOL);
+ out.textcolor(LIGHTGREY);
+ out.cprintf(EOL);
switch (action)
{
case RBOOK_USE_STAFF:
- cprintf( "Select a spell to cast." EOL );
+ out.cprintf( "Select a spell to cast." EOL );
break;
case RBOOK_MEMORIZE:
- cprintf( "Select a spell to memorise (%d level%s available)." EOL,
+ out.cprintf( "Select a spell to memorise (%d level%s available)." EOL,
spell_levels, (spell_levels == 1) ? "" : "s" );
break;
case RBOOK_READ_SPELL:
- cprintf( "Select a spell to read its description." EOL );
+ out.cprintf( "Select a spell to read its description." EOL );
break;
default:
break;
}
- unsigned char keyn = getch();
+ if (fs)
+ *fs = out;
- if (keyn == 0)
- getch();
+ unsigned char keyn = 0;
+ if (update_screen)
+ {
+#ifdef DOS_TERM
+ char buffer[4800];
+ gettext(1, 1, 80, 25, buffer);
+ window(1, 1, 80, 25);
+#endif
+ clrscr();
+
+ out.display();
+
+ keyn = getch();
+ if (keyn == 0)
+ getch();
#ifdef DOS_TERM
- puttext(1, 1, 80, 25, buffer);
- window(1, 18, 80, 25);
+ puttext(1, 1, 80, 25, buffer);
+ window(1, 18, 80, 25);
#endif
+ }
return (keyn); // try to figure out that for which this is used {dlb}
}
@@ -1244,7 +1257,7 @@ bool learn_spell(void)
index = letter_to_index( spell );
- if (index > SPELLBOOK_SIZE)
+ if (index >= SPELLBOOK_SIZE)
goto whatt;
if (!is_valid_spell_in_book( book, index ))
@@ -1415,6 +1428,31 @@ bool learn_spell(void)
return (true);
} // end which_spell()
+int count_staff_spells(const item_def &item, bool need_id)
+{
+ if (item.base_type != OBJ_STAVES)
+ return (-1);
+
+ if (need_id && item_not_ident( item, ISFLAG_KNOW_TYPE ))
+ return (0);
+
+ const int stype = item.sub_type;
+ const int type = stype + 40;
+ if (stype < STAFF_SMITING || stype >= STAFF_AIR)
+ return (0);
+
+ FixedVector< int, SPELLBOOK_SIZE > spell_list;
+ spellbook_template( type, spell_list );
+
+ int num_spells = 0;
+ for (num_spells = 0; num_spells < SPELLBOOK_SIZE - 1; num_spells++)
+ {
+ if (spell_list[ num_spells + 1 ] == SPELL_NO_SPELL)
+ break;
+ }
+ return (num_spells);
+}
+
int staff_spell( int staff )
{
int spell;
@@ -1480,7 +1518,7 @@ int staff_spell( int staff )
spell = letter_to_index( spell );
- if (spell > SPELLBOOK_SIZE)
+ if (spell >= SPELLBOOK_SIZE)
goto whattt;
if (!is_valid_spell_in_book( staff, spell ))
diff --git a/trunk/source/spl-book.h b/trunk/source/spl-book.h
index 359f2c052c..d6aa2843bc 100644
--- a/trunk/source/spl-book.h
+++ b/trunk/source/spl-book.h
@@ -60,4 +60,9 @@ int staff_spell( int zap_device_2 );
bool undead_cannot_memorise(unsigned char spell, unsigned char being);
+unsigned char spellbook_contents( item_def &book, int action,
+ formatted_string *fs = NULL );
+
+int count_staff_spells(const item_def &item, bool need_id);
+
#endif
diff --git a/trunk/source/spl-cast.cc b/trunk/source/spl-cast.cc
index 0ce8209786..6044904dc0 100644
--- a/trunk/source/spl-cast.cc
+++ b/trunk/source/spl-cast.cc
@@ -1949,6 +1949,22 @@ void exercise_spell( int spell, bool spc, bool success )
} // end exercise_spell()
+static bool send_abyss()
+{
+ if (you.level_type != LEVEL_ABYSS)
+ {
+ mpr("You are cast into the Abyss!");
+ more();
+ banished(DNGN_ENTER_ABYSS); // sends you to the abyss
+ return (true);
+ }
+ else
+ {
+ mpr("The world appears momentarily distorted.");
+ return (false);
+ }
+}
+
/* This determines how likely it is that more powerful wild magic effects
* will occur. Set to 100 for the old probabilities (although the individual
* effects have been made much nastier since then).
@@ -2332,9 +2348,7 @@ bool miscast_effect( unsigned int sp_type, int mag_pow, int mag_fail,
}
break;
case 6:
- mpr("You are cast into the Abyss!");
- more();
- banished(DNGN_ENTER_ABYSS); // sends you to the abyss
+ send_abyss();
break;
}
break;
@@ -2355,9 +2369,7 @@ bool miscast_effect( unsigned int sp_type, int mag_pow, int mag_fail,
potion_effect(POT_CONFUSION, 60);
break;
case 2:
- mpr("You are cast into the Abyss!");
- more();
- banished(DNGN_ENTER_ABYSS); // sends you to the abyss
+ send_abyss();
break;
case 3:
mpr("You feel saturated with unharnessed energies!");
@@ -2530,8 +2542,7 @@ bool miscast_effect( unsigned int sp_type, int mag_pow, int mag_fail,
break;
case 3:
- mpr("You are cast into the Abyss!");
- banished(DNGN_ENTER_ABYSS);
+ send_abyss();
break;
}
break;
diff --git a/trunk/source/stash.cc b/trunk/source/stash.cc
new file mode 100644
index 0000000000..c82dc2d408
--- /dev/null
+++ b/trunk/source/stash.cc
@@ -0,0 +1,1606 @@
+/*
+ * File: stash.cc
+ * Summary: Classes tracking player stashes
+ * Written by: Darshan Shaligram
+ */
+
+#include "AppHdr.h"
+#include "chardump.h"
+#include "clua.h"
+#include "describe.h"
+#include "itemname.h"
+#include "files.h"
+#include "invent.h"
+#include "items.h"
+#include "Kills.h"
+#include "libutil.h"
+#include "menu.h"
+#include "shopping.h"
+#include "spl-book.h"
+#include "stash.h"
+#include "stuff.h"
+#include "tags.h"
+#include "travel.h"
+
+#include <cctype>
+#include <cstdio>
+#include <fstream>
+#include <algorithm>
+
+#ifdef STASH_TRACKING
+
+#define ST_MAJOR_VER ((unsigned char) 4)
+#define ST_MINOR_VER ((unsigned char) 4)
+
+#define LUA_SEARCH_ANNOTATE "ch_stash_search_annotate_item"
+#define LUA_DUMP_ANNOTATE "ch_stash_dump_annotate_item"
+#define LUA_VIEW_ANNOTATE "ch_stash_view_annotate_item"
+
+std::string short_place_name(level_id id)
+{
+ return short_place_name(
+ get_packed_place(id.branch, id.depth, LEVEL_DUNGEON));
+}
+
+std::string userdef_annotate_item(const char *s, const item_def *item,
+ bool exclusive)
+{
+#ifdef CLUA_BINDINGS
+ if (exclusive)
+ lua_set_exclusive_item(item);
+ std::string ann;
+ clua.callfn(s, "u>s", item, &ann);
+ if (exclusive)
+ lua_set_exclusive_item(NULL);
+ return (ann);
+
+#else
+ return ("");
+#endif
+}
+
+std::string stash_annotate_item(const char *s,
+ const item_def *item,
+ bool exclusive = false)
+{
+ std::string text = userdef_annotate_item(s, item, exclusive);
+ if ( (item->base_type == OBJ_BOOKS &&
+ item_type_known(*item) &&
+ item->sub_type != BOOK_MANUAL &&
+ item->sub_type != BOOK_DESTRUCTION)
+ || count_staff_spells(*item, true) > 1 )
+ {
+ formatted_string fs;
+ item_def dup = *item;
+ spellbook_contents( dup,
+ item->base_type == OBJ_BOOKS?
+ RBOOK_READ_SPELL
+ : RBOOK_USE_STAFF,
+ &fs );
+ text += EOL;
+ text += fs.tostring(2, -2);
+ }
+ return text;
+}
+
+bool is_stash(int x, int y)
+{
+ LevelStashes *ls = stashes.find_current_level();
+ if (ls)
+ {
+ Stash *s = ls->find_stash(x, y);
+ return s && s->enabled;
+ }
+ return false;
+}
+
+void describe_stash(int x, int y)
+{
+ LevelStashes *ls = stashes.find_current_level();
+ if (ls)
+ {
+ Stash *s = ls->find_stash(x, y);
+ if (s)
+ {
+ std::string desc = "[Stash: "
+ + s->description() + "]";
+ mpr(desc.c_str());
+ }
+ }
+}
+
+static void fully_identify_item(item_def *item)
+{
+ if (!item || !is_valid_item(*item))
+ return;
+
+ set_ident_flags( *item, ISFLAG_IDENT_MASK );
+ if (item->base_type != OBJ_WEAPONS)
+ set_ident_type( item->base_type,
+ item->sub_type,
+ ID_KNOWN_TYPE );
+}
+
+static void save_item(FILE *file, const item_def &item)
+{
+ writeByte(file, item.base_type);
+ writeByte(file, item.sub_type);
+ writeShort(file, item.plus);
+ writeShort(file, item.plus2);
+ writeLong(file, item.special);
+ writeShort(file, item.quantity);
+ writeByte(file, item.colour);
+ writeLong(file, item.flags);
+}
+
+static void load_item(FILE *file, item_def &item)
+{
+ item.base_type = readByte(file);
+ item.sub_type = readByte(file);
+ item.plus = readShort(file);
+ item.plus2 = readShort(file);
+ item.special = readLong(file);
+ item.quantity = readShort(file);
+ item.colour = readByte(file);
+ item.flags = readLong(file);
+}
+
+bool Stash::aggressive_verify = true;
+std::vector<item_def> Stash::filters;
+
+Stash::Stash(int xp, int yp) : enabled(true), items()
+{
+ // First, fix what square we're interested in
+ if (xp == -1)
+ {
+ xp = you.x_pos;
+ yp = you.y_pos;
+ }
+ x = (unsigned char) xp;
+ y = (unsigned char) yp;
+ abspos = GXM * (int) y + x;
+
+ update();
+}
+
+bool Stash::are_items_same(const item_def &a, const item_def &b)
+{
+ return a.base_type == b.base_type &&
+ a.sub_type == b.sub_type &&
+ a.plus == b.plus &&
+ a.plus2 == b.plus2 &&
+ a.special == b.special &&
+ a.colour == b.colour &&
+ a.flags == b.flags &&
+ a.quantity == b.quantity;
+}
+
+void Stash::filter(const std::string &str)
+{
+ std::string base = str;
+
+ base.erase(base.find_last_not_of(" \n\t") + 1);
+ base.erase(0, base.find_first_not_of(" \n\t"));
+
+ unsigned char subc = 255;
+ std::string::size_type cpos = base.find(":", 0);
+ if (cpos != std::string::npos)
+ {
+ std::string subs = base.substr(cpos + 1);
+ subc = atoi(subs.c_str());
+
+ base = base.substr(0, cpos);
+ }
+
+ unsigned char basec = atoi(base.c_str());
+ filter(basec, subc);
+}
+
+void Stash::filter(unsigned char base, unsigned char sub)
+{
+ item_def item;
+ item.base_type = base;
+ item.sub_type = sub;
+
+ filters.push_back(item);
+}
+
+bool Stash::is_filtered(const item_def &item)
+{
+ for (int i = 0, count = filters.size(); i < count; ++i)
+ {
+ const item_def &filter = filters[i];
+ if (item.base_type == filter.base_type &&
+ (filter.sub_type == 255 ||
+ item.sub_type == filter.sub_type))
+ return true;
+ }
+ return false;
+}
+
+void Stash::update()
+{
+ int objl = igrd[x][y];
+ // If this is your position, you know what's on this square
+ if (x == you.x_pos && y == you.y_pos)
+ {
+ // Zap existing items
+ items.clear();
+
+ // Now, grab all items on that square and fill our vector
+ while (objl != NON_ITEM)
+ {
+ if (!is_filtered(mitm[objl]))
+ items.push_back(mitm[objl]);
+ objl = mitm[objl].link;
+ }
+
+ verified = true;
+ }
+ // If this is not your position, the only thing we can do is verify that
+ // what the player sees on the square is the first item in this vector.
+ else
+ {
+ if (objl == NON_ITEM)
+ {
+ items.clear();
+ verified = true;
+ return ;
+ }
+
+ // There's something on this square. Take a squint at it.
+ item_def &item = mitm[objl];
+
+ if (item.link == NON_ITEM) items.clear();
+
+ // We knew of nothing on this square, so we'll assume this is the
+ // only item here, but mark it as unverified unless we can see nothing
+ // under the item.
+ if (items.size() == 0)
+ {
+ if (!is_filtered(item))
+ items.push_back(item);
+ verified = (item.link == NON_ITEM);
+ return ;
+ }
+
+ // There's more than one item in this pile. As long as the top item is
+ // not filtered, we can check to see if it matches what we think the
+ // top item is.
+
+ if (is_filtered(item)) return;
+
+ item_def &first = items[0];
+ // Compare these items
+ if (!are_items_same(first, item))
+ {
+ if (aggressive_verify)
+ {
+ // See if 'item' matches any of the items we have. If it does,
+ // we'll just make that the first item and leave 'verified'
+ // unchanged.
+
+ // Start from 1 because we've already checked items[0]
+ for (int i = 1, count = items.size(); i < count; ++i)
+ {
+ if (are_items_same(items[i], item))
+ {
+ // Found it. Swap it to the front of the vector.
+ item_def temp = items[i];
+ items[i] = items[0];
+ items[0] = temp;
+
+ // We don't set verified to true. If this stash was
+ // already unverified, it remains so.
+ return;
+ }
+ }
+ }
+
+ // If this is unverified, forget last item on stack. This isn't
+ // terribly clever, but it prevents the vector swelling forever.
+ if (!verified) items.pop_back();
+
+ // Items are different. We'll put this item in the front of our
+ // vector, and mark this as unverified
+ items.insert(items.begin(), item);
+ verified = false;
+ }
+ }
+}
+
+// Returns the item name for a given item, with any appropriate
+// stash-tracking pre/suffixes.
+std::string Stash::stash_item_name(const item_def &item)
+{
+ char buf[ITEMNAME_SIZE];
+
+ if (item.base_type == OBJ_GOLD)
+ snprintf(buf, sizeof buf, "%d gold piece%s", item.quantity,
+ (item.quantity > 1? "s" : ""));
+ else
+ item_name(item, DESC_NOCAP_A, buf, false);
+
+ return buf;
+}
+
+class StashMenu : public Menu
+{
+public:
+ StashMenu() : Menu(MF_SINGLESELECT), can_travel(false) { }
+public:
+ bool can_travel;
+protected:
+ void draw_title();
+ bool process_key(int key);
+};
+
+void StashMenu::draw_title()
+{
+ if (title)
+ {
+ gotoxy(1, 1);
+ textcolor(title->colour);
+ cprintf(title->text.c_str());
+ if (title->quantity)
+ cprintf(", %d item%s", title->quantity,
+ title->quantity == 1? "" : "s");
+ cprintf(")");
+ if (can_travel)
+ cprintf(" [ENTER - travel]");
+ }
+}
+
+bool StashMenu::process_key(int key)
+{
+ if (key == CK_ENTER)
+ {
+ // Travel activates.
+ lastch = 1;
+ return false;
+ }
+ return Menu::process_key(key);
+}
+
+static void stash_menu_fixup(MenuEntry *me)
+{
+ const item_def *item = static_cast<const item_def *>( me->data );
+ if (item->base_type == OBJ_GOLD)
+ {
+ me->quantity = 0;
+ me->colour = DARKGREY;
+ }
+}
+
+bool Stash::show_menu(const std::string &prefix, bool can_travel) const
+{
+ StashMenu menu;
+
+ MenuEntry *mtitle = new MenuEntry(" Stash (" + prefix);
+ menu.can_travel = can_travel;
+ mtitle->colour = WHITE;
+ mtitle->quantity = items.size();
+ menu.set_title(mtitle);
+
+ populate_item_menu(&menu, items, stash_menu_fixup);
+ std::vector<MenuEntry*> sel;
+ while (true)
+ {
+ sel = menu.show();
+ if (menu.getkey() == 1)
+ return true;
+
+ if (sel.size() != 1)
+ break;
+
+ const item_def *item =
+ static_cast<const item_def *>( sel[0]->data );
+ describe_item(*item);
+ }
+ return false;
+}
+
+std::string Stash::description() const
+{
+ if (!enabled || items.empty())
+ return ("");
+
+ const item_def &item = items[0];
+ std::string desc = stash_item_name(item);
+
+ size_t sz = items.size();
+ if (sz > 1)
+ {
+ char additionals[50];
+ snprintf(additionals, sizeof additionals, " (+%u)", sz - 1);
+ desc += additionals;
+ }
+ return (desc);
+}
+
+bool Stash::matches_search(const std::string &prefix,
+ const base_pattern &search,
+ stash_search_result &res) const
+{
+ if (!enabled || items.empty()) return false;
+
+ for (unsigned i = 0; i < items.size(); ++i)
+ {
+ const item_def &item = items[i];
+ std::string s = stash_item_name(item);
+ std::string ann = stash_annotate_item(
+ LUA_SEARCH_ANNOTATE, &item);
+ if (search.matches(prefix + " " + ann + s))
+ {
+ if (!res.count++)
+ res.match = s;
+ res.matches += item.quantity;
+ continue;
+ }
+
+ if (is_dumpable_artifact(item, Options.verbose_dump))
+ {
+ std::string desc = munge_description(get_item_description(item,
+ Options.verbose_dump,
+ true ));
+ if (search.matches(desc))
+ {
+ if (!res.count++)
+ res.match = s;
+ res.matches += item.quantity;
+ }
+ }
+ }
+ if (res.matches)
+ {
+ res.stash = this;
+ res.pos.x = x;
+ res.pos.y = y;
+ }
+
+ return !!res.matches;
+}
+
+void Stash::write(std::ostream &os,
+ int refx, int refy,
+ std::string place,
+ bool identify)
+ const
+{
+ if (!enabled || (items.size() == 0 && verified)) return;
+ os << "(" << ((int) x - refx) << ", " << ((int) y - refy)
+ << (place.length()? ", " + place : "")
+ << ")"
+ << std::endl;
+
+ char buf[ITEMNAME_SIZE];
+ for (unsigned i = 0; i < items.size(); ++i)
+ {
+ item_def item = items[i];
+
+ if (identify)
+ fully_identify_item(&item);
+
+ std::string s = stash_item_name(item);
+ strncpy(buf, s.c_str(), sizeof buf);
+
+ std::string ann = userdef_annotate_item(
+ LUA_DUMP_ANNOTATE, &item);
+
+ if (!ann.empty())
+ {
+ trim_string(ann);
+ ann = " " + ann;
+ }
+
+ os << " " << buf
+ << (!ann.empty()? ann : std::string())
+ << (!verified && (items.size() > 1 || i)? " (still there?)" : "")
+ << std::endl;
+
+ if (is_dumpable_artifact(item, Options.verbose_dump))
+ {
+ std::string desc = munge_description(get_item_description(item,
+ Options.verbose_dump,
+ true ));
+ // Kill leading and trailing whitespace
+ desc.erase(desc.find_last_not_of(" \n\t") + 1);
+ desc.erase(0, desc.find_first_not_of(" \n\t"));
+ // If string is not-empty, pad out to a neat indent
+ if (desc.length())
+ {
+ // Walk backwards and prepend indenting spaces to \n characters
+ for (int i = desc.length() - 1; i >= 0; --i)
+ if (desc[i] == '\n')
+ desc.insert(i + 1, " ");
+ os << " " << desc << std::endl;
+ }
+ }
+ }
+
+ if (items.size() <= 1 && !verified)
+ os << " (unseen)" << std::endl;
+}
+
+void Stash::save(FILE *file) const
+{
+ // How many items on this square?
+ writeShort(file, (short) items.size());
+
+ writeByte(file, x);
+ writeByte(file, y);
+
+ // Note: Enabled save value is inverted logic, so that it defaults to true
+ writeByte(file,
+ (unsigned char) ((verified? 1 : 0) | (!enabled? 2 : 0)) );
+
+ // And dump the items individually. We don't bother saving fields we're
+ // not interested in (and don't anticipate being interested in).
+ for (unsigned i = 0; i < items.size(); ++i)
+ save_item(file, items[i]);
+}
+
+void Stash::load(FILE *file)
+{
+ // How many items?
+ int count = readShort(file);
+
+ x = readByte(file);
+ y = readByte(file);
+
+ unsigned char flags = readByte(file);
+ verified = (flags & 1) != 0;
+
+ // Note: Enabled save value is inverted so it defaults to true.
+ enabled = (flags & 2) == 0;
+
+ abspos = GXM * (int) y + x;
+
+ // Zap out item vector, in case it's in use (however unlikely)
+ items.clear();
+ // Read in the items
+ for (int i = 0; i < count; ++i)
+ {
+ item_def item;
+ load_item(file, item);
+
+ items.push_back(item);
+ }
+}
+
+std::ostream &operator << (std::ostream &os, const Stash &s)
+{
+ s.write(os);
+ return os;
+}
+
+ShopInfo::ShopInfo(int xp, int yp) : x(xp), y(yp), name(), shoptype(-1),
+ visited(false), items()
+{
+ // Most of our initialization will be done externally; this class is really
+ // a mildly glorified struct.
+ const shop_struct *sh = get_shop(x, y);
+ if (sh)
+ shoptype = sh->type;
+}
+
+ShopInfo::ShopId::ShopId(int stype) : shoptype(stype), id()
+{
+ shop_init_id_type( shoptype, id );
+}
+
+ShopInfo::ShopId::~ShopId()
+{
+ shop_uninit_id_type( shoptype, id );
+}
+
+void ShopInfo::add_item(const item_def &sitem, unsigned price)
+{
+ shop_item it;
+ it.item = sitem;
+ it.price = price;
+ items.push_back(it);
+}
+
+std::string ShopInfo::shop_item_name(const shop_item &si) const
+{
+ char shopitem[ITEMNAME_SIZE * 2];
+ std::string itemname = Stash::stash_item_name(si.item);
+ snprintf(shopitem, sizeof shopitem, "%s (%u gold)",
+ itemname.c_str(), si.price);
+ return shopitem;
+}
+
+std::string ShopInfo::shop_item_desc(const shop_item &si) const
+{
+ std::string desc;
+ if (is_dumpable_artifact(si.item, Options.verbose_dump))
+ {
+ desc = munge_description(get_item_description(si.item,
+ Options.verbose_dump,
+ true));
+
+ // trim leading whitespace
+ std::string::size_type notwhite = desc.find_first_not_of(" \t\n");
+ desc.erase(0, notwhite);
+
+ // trim trailing whitespace
+ notwhite = desc.find_last_not_of(" \t\n");
+ desc.erase(notwhite + 1);
+
+ // Walk backwards and prepend indenting spaces to \n characters
+ for (int i = desc.length() - 1; i >= 0; --i)
+ if (desc[i] == '\n')
+ desc.insert(i + 1, " ");
+ }
+ return desc;
+}
+
+void ShopInfo::describe_shop_item(const shop_item &si) const
+{
+ describe_item( si.item );
+}
+
+bool ShopInfo::show_menu(const std::string &place,
+ bool can_travel) const
+{
+ ShopId id(shoptype);
+ StashMenu menu;
+
+ MenuEntry *mtitle = new MenuEntry(" " + name + " (" + place);
+ menu.can_travel = can_travel;
+ mtitle->colour = WHITE;
+ mtitle->quantity = items.size();
+ menu.set_title(mtitle);
+
+ menu_letter hotkey;
+ if (items.empty())
+ {
+ MenuEntry *me = new MenuEntry(
+ visited? " (Shop is empty)" : " (Shop contents are unknown)",
+ MEL_ITEM,
+ 0,
+ 0);
+ me->colour = DARKGREY;
+ menu.add_entry(me);
+ }
+ else
+ {
+ for (int i = 0, count = items.size(); i < count; ++i)
+ {
+ MenuEntry *me = new MenuEntry(shop_item_name(items[i]),
+ MEL_ITEM,
+ 1,
+ hotkey++);
+ me->data = const_cast<shop_item *>( &items[i] );
+ menu.add_entry(me);
+ }
+ }
+
+ std::vector<MenuEntry*> sel;
+ while (true)
+ {
+ sel = menu.show();
+ if (menu.getkey() == 1)
+ return true;
+
+ if (sel.size() != 1)
+ break;
+
+ const shop_item *item =
+ static_cast<const shop_item *>( sel[0]->data );
+ describe_shop_item(*item);
+ }
+ return false;
+}
+
+std::string ShopInfo::description() const
+{
+ return (name);
+}
+
+bool ShopInfo::matches_search(const std::string &prefix,
+ const base_pattern &search,
+ stash_search_result &res) const
+{
+ if (items.empty() && visited) return false;
+
+ ShopId id(shoptype);
+ bool match = false;
+
+ for (unsigned i = 0; i < items.size(); ++i)
+ {
+ std::string name = shop_item_name(items[i]);
+ std::string ann = stash_annotate_item(
+ LUA_SEARCH_ANNOTATE, &items[i].item, true);
+
+ bool thismatch = false;
+ if (search.matches(prefix + " " + ann + name))
+ thismatch = true;
+ else
+ {
+ std::string desc = shop_item_desc(items[i]);
+ if (search.matches(desc))
+ thismatch = true;
+ }
+
+ if (thismatch)
+ {
+ if (!res.count++)
+ res.match = name;
+ res.matches++;
+ }
+ }
+
+ if (!res.matches)
+ {
+ std::string shoptitle = prefix + " {shop} " + name;
+ if (!visited && items.empty())
+ shoptitle += "*";
+ if (search.matches(shoptitle))
+ {
+ match = true;
+ res.match = name;
+ }
+ }
+
+ if (match || res.matches)
+ {
+ res.shop = this;
+ res.pos.x = x;
+ res.pos.y = y;
+ }
+
+ return match || res.matches;
+}
+
+void ShopInfo::write(std::ostream &os, bool identify) const
+{
+ ShopId id(shoptype);
+
+ os << "[Shop] " << name << std::endl;
+ if (items.size() > 0)
+ {
+ for (unsigned i = 0; i < items.size(); ++i)
+ {
+ shop_item item = items[i];
+
+ if (identify)
+ fully_identify_item(&item.item);
+
+ os << " " << shop_item_name(item) << std::endl;
+ std::string desc = shop_item_desc(item);
+ if (desc.length() > 0)
+ os << " " << desc << std::endl;
+ }
+ }
+ else if (visited)
+ os << " (Shop is empty)" << std::endl;
+ else
+ os << " (Shop contents are unknown)" << std::endl;
+}
+
+void ShopInfo::save(FILE *file) const
+{
+ writeShort(file, shoptype);
+
+ int mangledx = (short) x;
+ if (!visited)
+ mangledx |= 1024;
+ writeShort(file, mangledx);
+ writeShort(file, (short) y);
+
+ writeShort(file, (short) items.size());
+
+ writeString(file, name);
+
+ for (unsigned i = 0; i < items.size(); ++i)
+ {
+ save_item(file, items[i].item);
+ writeShort(file, (short) items[i].price );
+ }
+}
+
+void ShopInfo::load(FILE *file)
+{
+ shoptype = readShort(file);
+
+ x = readShort(file);
+ visited = !(x & 1024);
+ x &= 0xFF;
+
+ y = readShort(file);
+
+ int itemcount = readShort(file);
+
+ name = readString(file);
+ for (int i = 0; i < itemcount; ++i)
+ {
+ shop_item item;
+ load_item(file, item.item);
+ item.price = (unsigned) readShort(file);
+ items.push_back(item);
+ }
+}
+
+std::ostream &operator << (std::ostream &os, const ShopInfo &s)
+{
+ s.write(os);
+ return os;
+}
+
+LevelStashes::LevelStashes()
+{
+ branch = you.where_are_you;
+ depth = you.your_level;
+}
+
+bool LevelStashes::operator < (const LevelStashes &lev) const
+{
+ return branch < lev.branch || (branch == lev.branch && depth < lev.depth);
+}
+
+bool LevelStashes::isBelowPlayer() const
+{
+ return branch > you.where_are_you
+ || (branch == you.where_are_you && depth > you.your_level);
+}
+
+Stash *LevelStashes::find_stash(int x, int y)
+{
+ if (x == -1 || y == -1)
+ {
+ x = you.x_pos;
+ y = you.y_pos;
+ }
+ const int abspos = (GXM * y) + x;
+ c_stashes::iterator st = stashes.find(abspos);
+ return (st == stashes.end()? NULL : &st->second);
+}
+
+const ShopInfo *LevelStashes::find_shop(int x, int y) const
+{
+ for (unsigned i = 0; i < shops.size(); ++i)
+ {
+ if (shops[i].isAt(x, y))
+ return (&shops[i]);
+ }
+ return (NULL);
+}
+
+ShopInfo &LevelStashes::get_shop(int x, int y)
+{
+ for (unsigned i = 0; i < shops.size(); ++i)
+ {
+ if (shops[i].isAt(x, y))
+ return shops[i];
+ }
+ ShopInfo si(x, y);
+ si.set_name(shop_name(x, y));
+ shops.push_back(si);
+ return get_shop(x, y);
+}
+
+// Updates the stash at (x,y). Returns true if there was a stash at (x,y), false
+// otherwise.
+bool LevelStashes::update_stash(int x, int y)
+{
+ Stash *s = find_stash(x, y);
+ if (s)
+ {
+ s->update();
+ if (s->empty())
+ kill_stash(*s);
+ return true;
+ }
+ return false;
+}
+
+// Removes a Stash from the level.
+void LevelStashes::kill_stash(const Stash &s)
+{
+ stashes.erase(s.abs_pos());
+}
+
+void LevelStashes::no_stash(int x, int y)
+{
+ Stash *s = find_stash(x, y);
+ bool en = false;
+ if (s)
+ {
+ en = s->enabled = !s->enabled;
+ s->update();
+ if (s->empty())
+ kill_stash(*s);
+ }
+ else
+ {
+ Stash newStash(x, y);
+ newStash.enabled = false;
+
+ stashes[ newStash.abs_pos() ] = newStash;
+ }
+
+ mpr(en? "I'll no longer ignore what I see on this square."
+ : "Ok, I'll ignore what I see on this square.");
+}
+
+void LevelStashes::add_stash(int x, int y)
+{
+ Stash *s = find_stash(x, y);
+ if (s)
+ {
+ s->update();
+ if (s->empty())
+ kill_stash(*s);
+ }
+ else
+ {
+ Stash newStash(x, y);
+ if (!newStash.empty())
+ stashes[ newStash.abs_pos() ] = newStash;
+ }
+}
+
+bool LevelStashes::isCurrent() const
+{
+ return branch == you.where_are_you && depth == you.your_level;
+}
+
+bool LevelStashes::in_hell() const
+{
+ return branch >= BRANCH_DIS
+ && branch <= BRANCH_THE_PIT
+ && branch != BRANCH_VESTIBULE_OF_HELL;
+}
+
+bool LevelStashes::in_branch(int branchid) const
+{
+ return branch == branchid;
+}
+
+
+std::string LevelStashes::level_name() const
+{
+ int curr_subdungeon_level = subdungeon_depth( branch, depth );
+ return (branch_level_name(branch, curr_subdungeon_level));
+}
+
+std::string LevelStashes::short_level_name() const
+{
+ return (short_place_name(
+ get_packed_place( branch,
+ subdungeon_depth( branch, depth ),
+ LEVEL_DUNGEON ) ));
+}
+
+int LevelStashes::count_stashes() const
+{
+ int rawcount = stashes.size();
+ if (!rawcount)
+ return (0);
+
+ for (c_stashes::const_iterator iter = stashes.begin();
+ iter != stashes.end(); iter++)
+ {
+ if (!iter->second.enabled)
+ --rawcount;
+ }
+ return rawcount;
+}
+
+void LevelStashes::get_matching_stashes(
+ const base_pattern &search,
+ std::vector<stash_search_result> &results)
+ const
+{
+ level_id clid(branch, subdungeon_depth(branch, depth));
+ std::string lplace = "{" + short_place_name(clid) + "}";
+ for (c_stashes::const_iterator iter = stashes.begin();
+ iter != stashes.end(); iter++)
+ {
+ if (iter->second.enabled)
+ {
+ stash_search_result res;
+ if (iter->second.matches_search(lplace, search, res))
+ {
+ res.level = clid;
+ results.push_back(res);
+ }
+ }
+ }
+
+ for (unsigned i = 0; i < shops.size(); ++i)
+ {
+ stash_search_result res;
+ if (shops[i].matches_search(lplace, search, res))
+ {
+ res.level = clid;
+ results.push_back(res);
+ }
+ }
+}
+
+void LevelStashes::write(std::ostream &os, bool identify) const
+{
+ if (visible_stash_count() == 0) return ;
+ os << level_name() << std::endl;
+
+ for (unsigned i = 0; i < shops.size(); ++i)
+ {
+ shops[i].write(os, identify);
+ }
+
+ if (stashes.size())
+ {
+ const Stash &s = stashes.begin()->second;
+ int refx = s.getX(), refy = s.getY();
+ std::string level_name = short_level_name();
+ for (c_stashes::const_iterator iter = stashes.begin();
+ iter != stashes.end(); iter++)
+ {
+ iter->second.write(os, refx, refy, level_name, identify);
+ }
+ }
+ os << std::endl;
+}
+
+void LevelStashes::save(FILE *file) const
+{
+ // How many stashes on this level?
+ writeShort(file, (short) stashes.size());
+
+ writeByte(file, branch);
+ writeShort(file, (short) depth);
+
+ // And write the individual stashes
+ for (c_stashes::const_iterator iter = stashes.begin();
+ iter != stashes.end(); iter++)
+ iter->second.save(file);
+
+ writeShort(file, (short) shops.size());
+ for (unsigned i = 0; i < shops.size(); ++i)
+ shops[i].save(file);
+}
+
+void LevelStashes::load(FILE *file)
+{
+ int size = readShort(file);
+
+ branch = readByte(file);
+ depth = readShort(file);
+
+ stashes.clear();
+ for (int i = 0; i < size; ++i)
+ {
+ Stash s;
+ s.load(file);
+ if (!s.empty())
+ stashes[ s.abs_pos() ] = s;
+ }
+
+ shops.clear();
+ int shopc = readShort(file);
+ for (int i = 0; i < shopc; ++i)
+ {
+ ShopInfo si(0, 0);
+ si.load(file);
+ shops.push_back(si);
+ }
+}
+
+std::ostream &operator << (std::ostream &os, const LevelStashes &ls)
+{
+ ls.write(os);
+ return os;
+}
+
+LevelStashes &StashTracker::get_current_level()
+{
+ std::vector<LevelStashes>::iterator iter = levels.begin();
+ for ( ; iter != levels.end() && !iter->isBelowPlayer(); iter++)
+ {
+ if (iter->isCurrent()) return *iter;
+ }
+ if (iter == levels.end())
+ levels.push_back(LevelStashes());
+ else
+ levels.insert(iter, LevelStashes());
+ return get_current_level();
+}
+
+LevelStashes *StashTracker::find_current_level()
+{
+ std::vector<LevelStashes>::iterator iter = levels.begin();
+ for ( ; iter != levels.end() && !iter->isBelowPlayer(); iter++)
+ {
+ if (iter->isCurrent()) return &*iter;
+ }
+ return NULL;
+}
+
+
+bool StashTracker::update_stash(int x, int y)
+{
+ LevelStashes *lev = find_current_level();
+ if (lev)
+ {
+ bool res = lev->update_stash(x, y);
+ if (!lev->stash_count())
+ remove_level(*lev);
+ return res;
+ }
+ return false;
+}
+
+void StashTracker::remove_level(const LevelStashes &ls)
+{
+ std::vector<LevelStashes>::iterator iter = levels.begin();
+ for ( ; iter != levels.end(); ++iter)
+ {
+ if (&ls == &*iter)
+ {
+ levels.erase(iter);
+ break ;
+ }
+ }
+}
+
+void StashTracker::no_stash(int x, int y)
+{
+ if (is_level_untrackable())
+ return ;
+ LevelStashes &current = get_current_level();
+ current.no_stash(x, y);
+ if (!current.stash_count())
+ remove_level(current);
+}
+
+void StashTracker::add_stash(int x, int y, bool verbose)
+{
+ if (is_level_untrackable())
+ return ;
+ LevelStashes &current = get_current_level();
+ current.add_stash(x, y);
+
+ if (verbose)
+ {
+ Stash *s = current.find_stash(x, y);
+ if (s && s->enabled)
+ mpr("Added stash.");
+ }
+
+ if (!current.stash_count())
+ remove_level(current);
+}
+
+void StashTracker::dump(const char *filename, bool identify) const
+{
+ std::ofstream outf(filename);
+ if (outf)
+ {
+ write(outf, identify);
+ outf.close();
+ }
+}
+
+void StashTracker::write(std::ostream &os, bool identify) const
+{
+ os << you.your_name << std::endl << std::endl;
+ if (!levels.size())
+ os << " You have no stashes." << std::endl;
+ else
+ {
+ std::vector<LevelStashes>::const_iterator iter = levels.begin();
+ for ( ; iter != levels.end(); iter++)
+ {
+ iter->write(os, identify);
+ }
+ }
+}
+
+void StashTracker::save(FILE *file) const
+{
+ // Write version info first - major + minor
+ writeByte(file, ST_MAJOR_VER);
+ writeByte(file, ST_MINOR_VER);
+
+ // How many levels have we?
+ writeShort(file, (short) levels.size());
+
+ // And ask each level to write itself to the tag
+ std::vector<LevelStashes>::const_iterator iter = levels.begin();
+ for ( ; iter != levels.end(); iter++)
+ iter->save(file);
+}
+
+void StashTracker::load(FILE *file)
+{
+ // Check version. Compatibility isn't important, since stash-tracking
+ // is non-critical.
+ unsigned char major = readByte(file),
+ minor = readByte(file);
+ if (major != ST_MAJOR_VER || minor != ST_MINOR_VER) return ;
+
+ int count = readShort(file);
+
+ levels.clear();
+ for (int i = 0; i < count; ++i)
+ {
+ LevelStashes st;
+ st.load(file);
+ if (st.stash_count()) levels.push_back(st);
+ }
+}
+
+void StashTracker::update_visible_stashes(
+ StashTracker::stash_update_mode mode)
+{
+ if (is_level_untrackable())
+ return ;
+
+ LevelStashes *lev = find_current_level();
+ for (int cy = 1; cy <= 17; ++cy)
+ {
+ for (int cx = 9; cx <= 25; ++cx)
+ {
+ int x = you.x_pos + cx - 17, y = you.y_pos + cy - 9;
+ if (x < 0 || x >= GXM || y < 0 || y >= GYM)
+ continue;
+
+ if (!env.show[cx - 8][cy] && !(cx == 17 && cy == 9))
+ continue;
+
+ if ((!lev || !lev->update_stash(x, y))
+ && mode == ST_AGGRESSIVE
+ && igrd[x][y] != NON_ITEM)
+ {
+ if (!lev)
+ lev = &get_current_level();
+ lev->add_stash(x, y);
+ }
+
+ if (grd[x][y] == DNGN_ENTER_SHOP)
+ get_shop(x, y);
+ }
+ }
+
+ if (lev && !lev->stash_count())
+ remove_level(*lev);
+}
+
+#define SEARCH_SPAM_THRESHOLD 400
+static std::string lastsearch;
+static input_history search_history(15);
+
+void StashTracker::search_stashes()
+{
+ char prompt[200];
+ if (lastsearch.length())
+ snprintf(prompt, sizeof prompt,
+ "Search your stashes for what item [Enter for \"%s\"]?",
+ lastsearch.c_str());
+ else
+ snprintf(prompt, sizeof prompt,
+ "Search your stashes for what item?");
+
+ mpr(prompt, MSGCH_PROMPT);
+ // Push the cursor down to the next line.
+ mpr("", MSGCH_PROMPT);
+
+ char buf[400];
+ bool validline = cancelable_get_line(buf, sizeof buf, 80, &search_history);
+ mesclr();
+ if (!validline || (!*buf && !lastsearch.length()))
+ return;
+
+ std::string csearch = *buf? buf : lastsearch;
+ std::vector<stash_search_result> results;
+
+ base_pattern *search = NULL;
+
+ text_pattern tpat( csearch, true );
+ search = &tpat;
+
+#ifdef CLUA_BINDINGS
+ lua_text_pattern ltpat( csearch );
+
+ if (lua_text_pattern::is_lua_pattern(csearch))
+ search = &ltpat;
+#endif
+
+ if (!search->valid())
+ {
+ mpr("Your search expression is invalid.", MSGCH_PLAIN);
+ return ;
+ }
+
+ lastsearch = csearch;
+
+ get_matching_stashes(*search, results);
+
+ if (results.empty())
+ {
+ mpr("That item is not present in any of your stashes.",
+ MSGCH_PLAIN);
+ return;
+ }
+
+ if (results.size() > SEARCH_SPAM_THRESHOLD)
+ {
+ mpr("Too many matches; use a more specific search.", MSGCH_PLAIN);
+ return ;
+ }
+
+ display_search_results(results);
+}
+
+void StashTracker::get_matching_stashes(
+ const base_pattern &search,
+ std::vector<stash_search_result> &results)
+ const
+{
+ std::vector<LevelStashes>::const_iterator iter = levels.begin();
+ for ( ; iter != levels.end(); iter++)
+ {
+ iter->get_matching_stashes(search, results);
+ if (results.size() > SEARCH_SPAM_THRESHOLD)
+ return;
+ }
+
+ level_id curr = level_id::get_current_level_id();
+ for (unsigned i = 0; i < results.size(); ++i)
+ {
+ results[i].player_distance = level_distance(curr, results[i].level);
+ }
+
+ // Sort stashes so that closer stashes come first and stashes on the same
+ // levels with more items come first.
+ std::sort(results.begin(), results.end());
+}
+
+class StashSearchMenu : public Menu
+{
+public:
+ StashSearchMenu() : Menu(), can_travel(true), meta_key(0) { }
+
+public:
+ bool can_travel;
+ int meta_key;
+
+protected:
+ bool process_key(int key);
+ void draw_title();
+};
+
+void StashSearchMenu::draw_title()
+{
+ if (title)
+ {
+ gotoxy(1, 1);
+ textcolor(title->colour);
+ cprintf(" %d %s%s", title->quantity, title->text.c_str(),
+ title->quantity > 1? "es" : "");
+
+ if (meta_key)
+ draw_title_suffix(" (x - examine stash)", false);
+ else
+ draw_title_suffix(" (x - go to stash; ? - examine stash)", false);
+ }
+}
+
+bool StashSearchMenu::process_key(int key)
+{
+ if (key == '?')
+ {
+ if (sel)
+ sel->clear();
+ meta_key = !meta_key;
+ update_title();
+ return true;
+ }
+
+ return Menu::process_key(key);
+}
+
+void StashTracker::display_search_results(
+ std::vector<stash_search_result> &results)
+{
+ if (results.empty())
+ return;
+
+ bool travelable = can_travel_interlevel();
+
+ StashSearchMenu stashmenu;
+ stashmenu.can_travel = travelable;
+ std::string title = "matching stash";
+
+ MenuEntry *mtitle = new MenuEntry(title);
+ mtitle->colour = WHITE;
+ // Abuse of the quantity field.
+ mtitle->quantity = results.size();
+ stashmenu.set_title(mtitle);
+
+ menu_letter hotkey;
+ for (unsigned i = 0; i < results.size(); ++i, ++hotkey)
+ {
+ stash_search_result &res = results[i];
+ char matchtitle[ITEMNAME_SIZE];
+ std::string place = short_place_name(res.level);
+ if (res.matches > 1 && res.count > 1)
+ {
+ snprintf(matchtitle, sizeof matchtitle,
+ "[%s] %s (%d)",
+ place.c_str(),
+ res.match.c_str(),
+ res.matches);
+ }
+ else
+ {
+ snprintf(matchtitle, sizeof matchtitle,
+ "[%s] %s",
+ place.c_str(),
+ res.match.c_str());
+ }
+ std::string mename = matchtitle;
+
+ MenuEntry *me = new MenuEntry(mename, MEL_ITEM, 1, hotkey);
+ me->data = &res;
+ if (res.shop && !res.shop->is_visited())
+ me->colour = CYAN;
+ stashmenu.add_entry(me);
+ }
+
+ stashmenu.set_flags( MF_SINGLESELECT );
+
+ std::vector<MenuEntry*> sel;
+ while (true)
+ {
+ sel = stashmenu.show();
+
+ if (sel.size() == 1 && stashmenu.meta_key)
+ {
+ stash_search_result *res =
+ static_cast<stash_search_result *>(sel[0]->data);
+
+ bool dotravel = false;
+ if (res->shop)
+ {
+ dotravel = res->shop->show_menu(short_place_name(res->level),
+ travelable);
+ }
+ else if (res->stash)
+ {
+ dotravel = res->stash->show_menu(short_place_name(res->level),
+ travelable);
+ }
+
+ if (dotravel && travelable)
+ {
+ redraw_screen();
+ level_pos lp;
+ lp.id = res->level;
+ lp.pos = res->pos;
+ start_translevel_travel(lp);
+ return ;
+ }
+ continue;
+ }
+ break;
+ }
+
+ redraw_screen();
+ if (travelable && sel.size() == 1 && !stashmenu.meta_key)
+ {
+ const stash_search_result *res =
+ static_cast<stash_search_result *>(sel[0]->data);
+ level_pos lp;
+ lp.id = res->level;
+ lp.pos = res->pos;
+ start_translevel_travel(lp);
+ return ;
+ }
+
+}
+
+// Global
+StashTracker stashes;
+
+#endif
+
+std::string branch_level_name(unsigned char branch, int sub_depth)
+{
+ int ltype = sub_depth == 0xFF? branch : 0;
+ if (ltype == LEVEL_PANDEMONIUM)
+ return ("Pandemonium");
+ else if (ltype == LEVEL_ABYSS)
+ return ("The Abyss");
+ else if (ltype == LEVEL_LABYRINTH)
+ return ("A Labyrinth");
+ else
+ {
+ char buf[200];
+ const char *s = NULL;
+ *buf = 0;
+ // level_type == LEVEL_DUNGEON
+ if (branch != BRANCH_VESTIBULE_OF_HELL
+ && branch != BRANCH_ECUMENICAL_TEMPLE
+ && branch != BRANCH_HALL_OF_BLADES)
+ snprintf(buf, sizeof buf, "Level %d", sub_depth);
+
+ switch (branch)
+ {
+ case BRANCH_MAIN_DUNGEON:
+ s = " of the Dungeon";
+ break;
+ case BRANCH_DIS:
+ s = " of Dis";
+ break;
+ case BRANCH_GEHENNA:
+ s = " of Gehenna";
+ break;
+ case BRANCH_VESTIBULE_OF_HELL:
+ s = "The Vestibule of Hell";
+ break;
+ case BRANCH_COCYTUS:
+ s = " of Cocytus";
+ break;
+ case BRANCH_TARTARUS:
+ s = " of Tartarus";
+ break;
+ case BRANCH_INFERNO:
+ s = " of the Inferno";
+ break;
+ case BRANCH_THE_PIT:
+ s = " of the Pit";
+ break;
+ case BRANCH_ORCISH_MINES:
+ s = " of the Orcish Mines";
+ break;
+ case BRANCH_HIVE:
+ s = " of the Hive";
+ break;
+ case BRANCH_LAIR:
+ s = " of the Lair";
+ break;
+ case BRANCH_SLIME_PITS:
+ s = " of the Slime Pits";
+ break;
+ case BRANCH_VAULTS:
+ s = " of the Vaults";
+ break;
+ case BRANCH_CRYPT:
+ s = " of the Crypt";
+ break;
+ case BRANCH_HALL_OF_BLADES:
+ s = "The Hall of Blades";
+ break;
+ case BRANCH_HALL_OF_ZOT:
+ s = " of the Realm of Zot";
+ break;
+ case BRANCH_ECUMENICAL_TEMPLE:
+ s = "The Ecumenical Temple";
+ break;
+ case BRANCH_SNAKE_PIT:
+ s = " of the Snake Pit";
+ break;
+ case BRANCH_ELVEN_HALLS:
+ s = " of the Elven Halls";
+ break;
+ case BRANCH_TOMB:
+ s = " of the Tomb";
+ break;
+ case BRANCH_SWAMP:
+ s = " of the Swamp";
+ break;
+ }
+ if (s)
+ strncat(buf, s, sizeof(buf) - 1);
+ return (buf);
+ }
+}
+
+std::string branch_level_name(unsigned short packed_place)
+{
+ return branch_level_name(packed_place >> 8, packed_place & 0xFF);
+}
diff --git a/trunk/source/stash.h b/trunk/source/stash.h
new file mode 100644
index 0000000000..5e36eb4a61
--- /dev/null
+++ b/trunk/source/stash.h
@@ -0,0 +1,327 @@
+/*
+ * File: stash.h
+ * Summary: Classes tracking player stashes
+ * Written by: Darshan Shaligram
+ */
+#ifndef STASH_H
+#define STASH_H
+
+#include "AppHdr.h"
+#include "shopping.h"
+#include <string>
+
+#ifdef STASH_TRACKING
+
+#include <iostream>
+#include <map>
+#include <vector>
+
+#include "externs.h"
+#include "travel.h"
+
+// Stash definitions
+
+enum STASH_TRACK_MODES
+{
+ STM_NONE, // Stashes are not tracked
+ STM_EXPLICIT, // Only explicitly marked stashes are tracked
+ STM_DROPPED, // Dropped items and explicitly marked stashes are
+ // tracked
+ STM_ALL // All seen items are tracked
+};
+
+struct stash_search_result;
+class Stash
+{
+public:
+ Stash(int xp = -1, int yp = -1);
+
+ static void filter(unsigned char base_type, unsigned char sub_type);
+ static void filter(const std::string &filt);
+
+ static std::string stash_item_name(const item_def &item);
+ void update();
+ void save(FILE*) const;
+ void load(FILE*);
+
+ std::string description() const;
+
+ bool show_menu(const std::string &place, bool can_travel) const;
+
+ bool matches_search(const std::string &prefix,
+ const base_pattern &search,
+ stash_search_result &res)
+ const;
+
+ void write(std::ostream &os, int refx = 0, int refy = 0,
+ std::string place = "",
+ bool identify = false) const;
+
+ bool empty() const
+ {
+ return items.empty() && enabled;
+ }
+
+ bool isAt(int xp, int yp) const { return x == xp && y == yp; }
+ int abs_pos() const { return abspos; }
+ int getX() const { return (int) x; }
+ int getY() const { return (int) y; }
+
+ bool is_verified() const { return verified; }
+
+ bool enabled; // If false, this Stash will neither track
+ // items nor dump itself. Disabled stashes are
+ // also never removed from the level's map of
+ // stashes.
+private:
+ bool verified; // Is this correct to the best of our knowledge?
+ unsigned char x, y;
+ int abspos;
+ std::vector<item_def> items;
+
+ /*
+ * If true (the default), the stash-tracker is a lot more likely to consider
+ * squares that the player is not standing on as correctly-verified. This
+ * will lead to cleaner dumps, but the chance of stashes not accurately
+ * reflecting what's in the dungeon also increases.
+ */
+ static bool aggressive_verify;
+ static std::vector<item_def> filters;
+
+ static bool are_items_same(const item_def &, const item_def &);
+ static bool is_filtered(const item_def &item);
+};
+
+class ShopInfo
+{
+public:
+ ShopInfo(int xp, int yp);
+
+ bool matches_search(const std::string &prefix,
+ const base_pattern &search,
+ stash_search_result &res)
+ const;
+
+ std::string description() const;
+
+ // Note that we aren't bothering to use virtual functions here.
+ void save(FILE*) const;
+ void load(FILE*);
+
+ bool show_menu(const std::string &place, bool can_travel) const;
+ bool is_visited() const { return items.size() || visited; }
+
+ void write(std::ostream &os, bool identify = false) const;
+
+ void reset() { items.clear(); visited = true; }
+ void set_name(const char *s) { name = s; }
+
+ void add_item(const item_def &item, unsigned price);
+
+ bool isAt(int xp, int yp) const { return x == xp && y == yp; }
+
+private:
+ int x, y;
+ std::string name;
+
+ int shoptype;
+
+ // Set true if the player has visited this shop
+ bool visited;
+
+ // Messy!
+ struct shop_item
+ {
+ item_def item;
+ unsigned price;
+ };
+ std::vector<shop_item> items;
+
+ std::string shop_item_name(const shop_item &si) const;
+ std::string shop_item_desc(const shop_item &si) const;
+ void describe_shop_item(const shop_item &si) const;
+
+ class ShopId {
+ public:
+ ShopId(int stype);
+ ~ShopId();
+ private:
+ int shoptype;
+ id_fix_arr id;
+ };
+};
+
+struct stash_search_result
+{
+ // What level is this stash or shop on.
+ level_id level;
+
+ coord_def pos;
+
+ // Number of levels the player must cross to reach the level the stash/shop
+ // is on.
+ int player_distance;
+
+ // Number of items in the stash that match the search.
+ int matches;
+
+ // Count of items stacks in the stash that matched. This will be the same as
+ // matches if each matching stack's quantity is 1.
+ int count;
+
+ // First item in stash that matched
+ std::string match;
+
+ // The stash or shop in question.
+ const Stash *stash;
+ const ShopInfo *shop;
+
+ stash_search_result() : level(), player_distance(0), matches(0),
+ count(0), match(), stash(NULL), shop(NULL)
+ {
+ }
+
+ bool operator < ( const stash_search_result &ssr ) const
+ {
+ return player_distance < ssr.player_distance ||
+ (player_distance == ssr.player_distance &&
+ matches > ssr.matches);
+ }
+};
+
+extern std::ostream &operator << (std::ostream &, const Stash &);
+
+class LevelStashes
+{
+public:
+ LevelStashes();
+
+ Stash *find_stash(int x = -1, int y = -1);
+ ShopInfo &get_shop(int x, int y);
+ const ShopInfo *find_shop(int x, int y) const;
+
+ void get_matching_stashes(const base_pattern &search,
+ std::vector<stash_search_result> &results) const;
+
+ // Update stash at (x,y) on current level, defaulting to player's current
+ // location if no parameters are supplied.
+ bool update_stash(int x = -1, int y = -1);
+
+ // Add stash at (x,y), or player's current location if no parameters are
+ // supplied
+ void add_stash(int x = -1, int y = -1);
+
+ // Mark square (x,y) as not stashworthy. The player's current location is
+ // used if no parameters are supplied.
+ void no_stash(int x = -1, int y = -1);
+
+ void kill_stash(const Stash &s);
+
+ void save(FILE *) const;
+ void load(FILE *);
+
+ void write(std::ostream &os, bool identify = false) const;
+ std::string level_name() const;
+ std::string short_level_name() const;
+ bool in_hell() const;
+ bool in_branch(int) const;
+
+ int stash_count() const { return stashes.size() + shops.size(); }
+ int visible_stash_count() const { return count_stashes() + shops.size(); }
+
+ bool isCurrent() const;
+ bool isBelowPlayer() const;
+ bool operator < (const LevelStashes &lev) const;
+private:
+ // which level
+ unsigned char branch;
+ int depth;
+
+ typedef std::map<int, Stash> c_stashes;
+ typedef std::vector<ShopInfo> c_shops;
+
+ c_stashes stashes;
+ c_shops shops;
+
+ int count_stashes() const;
+};
+
+extern std::ostream &operator << (std::ostream &, const LevelStashes &);
+
+class StashTracker
+{
+public:
+ static bool is_level_untrackable()
+ {
+ return you.level_type == LEVEL_LABYRINTH
+ || you.level_type == LEVEL_ABYSS;
+ }
+
+ StashTracker() : levels()
+ {
+ }
+
+ void search_stashes();
+
+ LevelStashes &get_current_level();
+ LevelStashes *find_current_level();
+
+ ShopInfo &get_shop(int x, int y)
+ {
+ return get_current_level().get_shop(x, y);
+ }
+
+ void remove_level(const LevelStashes &ls);
+
+ enum stash_update_mode
+ {
+ ST_PASSIVE, // Maintain existing stashes only.
+ ST_AGGRESSIVE // Create stashes for each square containing
+ // objects, even if those squares were not
+ // previously marked as stashes.
+ };
+
+ void update_visible_stashes(StashTracker::stash_update_mode = ST_PASSIVE);
+
+ // Update stash at (x,y) on current level, defaulting to player's current
+ // location if no parameters are supplied, return true if a stash was
+ // updated.
+ bool update_stash(int x = -1, int y = -1);
+
+ // Add stash at (x,y), or player's current location if no parameters are
+ // supplied
+ void add_stash(int x = -1, int y = -1, bool verbose = false);
+
+ // Mark square (x,y) as not stashworthy. The player's current location is
+ // used if no parameters are supplied.
+ void no_stash(int x = -1, int y = -1);
+
+ void save(FILE*) const;
+ void load(FILE*);
+
+ void write(std::ostream &os, bool identify = false) const;
+
+ void dump(const char *filename, bool identify = false) const;
+private:
+ std::vector<LevelStashes> levels;
+
+ void get_matching_stashes(const base_pattern &search,
+ std::vector<stash_search_result> &results) const;
+ void display_search_results(std::vector<stash_search_result> &results);
+};
+
+extern StashTracker stashes;
+
+bool is_stash(int x, int y);
+void describe_stash(int x, int y);
+
+#endif // STASH_TRACKING
+
+std::string branch_level_name(unsigned char branch, int sub_depth);
+
+std::string branch_level_name(unsigned short packed_place);
+
+std::string userdef_annotate_item(const char *s, const item_def *item,
+ bool exclusive = false);
+
+#endif
diff --git a/trunk/source/stuff.cc b/trunk/source/stuff.cc
index c060fabe31..3a0a281d34 100644
--- a/trunk/source/stuff.cc
+++ b/trunk/source/stuff.cc
@@ -21,6 +21,22 @@
#include <string.h>
#include <ctype.h>
+#include <time.h>
+
+#include <stack>
+
+#ifdef USE_MORE_SECURE_SEED
+
+// for times()
+#include <sys/times.h>
+
+// for getpid()
+#include <sys/types.h>
+#include <unistd.h>
+
+#endif
+
+
// may need this later for something else {dlb}:
// required for table_lookup() {dlb}
//#include <stdarg.h>
@@ -40,6 +56,8 @@
#include "misc.h"
#include "monstuff.h"
#include "mon-util.h"
+#include "mt19937ar.h"
+#include "player.h"
#include "output.h"
#include "skills2.h"
#include "view.h"
@@ -52,7 +70,8 @@ unsigned long cfseed;
extern unsigned char (*mapch) (unsigned char);
// Crude, but functional.
-char *const make_time_string( time_t abs_time, char *const buff, int buff_size )
+char *const make_time_string( time_t abs_time, char *const buff, int buff_size,
+ bool terse )
{
const int days = abs_time / 86400;
const int hours = (abs_time % 86400) / 3600;
@@ -63,7 +82,10 @@ char *const make_time_string( time_t abs_time, char *const buff, int buff_size )
if (days > 0)
{
- snprintf( day_buff, sizeof(day_buff), "%d day%s, ",
+ if (terse)
+ snprintf(day_buff, sizeof day_buff, "%d, ", days);
+ else
+ snprintf( day_buff, sizeof(day_buff), "%d day%s, ",
days, (days > 1) ? "s" : "" );
}
@@ -156,6 +178,31 @@ unsigned char get_ch(void)
return gotched;
} // end get_ch()
+void seed_rng(long seed)
+{
+#ifdef USE_SYSTEM_RAND
+ srand(seed);
+#else
+ // MT19937 -- see mt19937ar.cc for details/licence
+ init_genrand(seed);
+#endif
+}
+
+void seed_rng()
+{
+ unsigned long seed = time( NULL );
+#ifdef USE_MORE_SECURE_SEED
+ struct tms buf;
+ seed += times( &buf ) + getpid();
+#endif
+
+ seed_rng(seed);
+#ifdef USE_SYSTEM_RAND
+ cf_setseed();
+#endif
+}
+
+#ifdef USE_SYSTEM_RAND
int random2(int max)
{
#ifdef USE_NEW_RANDOM
@@ -178,6 +225,136 @@ int random2(int max)
#endif
}
+// required for stuff::coinflip()
+#define IB1 1
+#define IB2 2
+#define IB5 16
+#define IB18 131072
+#define MASK (IB1 + IB2 + IB5)
+// required for stuff::coinflip()
+
+// I got to thinking a bit more about how much people talk
+// about RNGs and RLs and also about the issue of performance
+// when it comes to Crawl's RNG ... turning to *Numerical
+// Recipies in C* (Chapter 7-4, page 298), I hit upon what
+// struck me as a fine solution.
+
+// You can read all the details about this function (pretty
+// much stolen shamelessly from NRinC) elsewhere, but having
+// tested it out myself I think it satisfies Crawl's incessant
+// need to decide things on a 50-50 flip of the coin. No call
+// to random2() required -- along with all that wonderful math
+// and type casting -- and only a single variable its pointer,
+// and some bitwise operations to randomly generate 1s and 0s!
+// No parameter passing, nothing. Too good to be true, but it
+// works as long as cfseed is not set to absolute zero when it
+// is initialized ... good for 2**n-1 random bits before the
+// pattern repeats (n = long's bitlength on your platform).
+// It also avoids problems with poor implementations of rand()
+// on some platforms in regards to low-order bits ... a big
+// problem if one is only looking for a 1 or a 0 with random2()!
+
+// Talk about a hard sell! Anyway, it returns bool, so please
+// use appropriately -- I set it to bool to prevent such
+// tomfoolery, as I think that pure RNG and quickly grabbing
+// either a value of 1 or 0 should be separated where possible
+// to lower overhead in Crawl ... at least until it assembles
+// itself into something a bit more orderly :P 16jan2000 {dlb}
+
+// NB(1): cfseed is defined atop stuff.cc
+// NB(2): IB(foo) and MASK are defined somewhere in defines.h
+// NB(3): the function assumes that cf_setseed() has been called
+// beforehand - the call is presently made in acr::initialise()
+// right after srandom() and srand() are called (note also
+// that cf_setseed() requires rand() - random2 returns int
+// but a long can't hurt there).
+bool coinflip(void)
+{
+ extern unsigned long cfseed; // defined atop stuff.cc
+ unsigned long *ptr_cfseed = &cfseed;
+
+ if (*ptr_cfseed & IB18)
+ {
+ *ptr_cfseed = ((*ptr_cfseed ^ MASK) << 1) | IB1;
+ return true;
+ }
+ else
+ {
+ *ptr_cfseed <<= 1;
+ return false;
+ }
+} // end coinflip()
+
+// cf_setseed should only be called but once in all of Crawl!!! {dlb}
+void cf_setseed(void)
+{
+ extern unsigned long cfseed; // defined atop stuff.cc
+ unsigned long *ptr_cfseed = &cfseed;
+
+ do
+ {
+ // using rand() here makes these predictable -- bwr
+ *ptr_cfseed = rand();
+ }
+ while (*ptr_cfseed == 0);
+}
+
+static std::stack<long> rng_states;
+void push_rng_state()
+{
+ // XXX: Does this even work? randart.cc uses it, but I can't find anything
+ // that says this will restore the RNG to its original state. Anyway, we're
+ // now using MT with a deterministic push/pop.
+ rng_states.push(rand());
+}
+
+void pop_rng_state()
+{
+ if (!rng_states.empty())
+ {
+ seed_rng(rng_states.top());
+ rng_states.pop();
+ }
+}
+
+unsigned long random_int( void )
+{
+ return rand();
+}
+
+#else // USE_SYSTEM_RAND
+
+// MT19937 -- see mt19937ar.cc for details
+unsigned long random_int( void )
+{
+ return (genrand_int32());
+}
+
+int random2( int max )
+{
+ if (max <= 1)
+ return (0);
+
+ return (static_cast<int>( genrand_int32() / (0xFFFFFFFFUL / max + 1) ));
+}
+
+bool coinflip( void )
+{
+ return (static_cast<bool>( random2(2) ));
+}
+
+void push_rng_state()
+{
+ push_mt_state();
+}
+
+void pop_rng_state()
+{
+ pop_mt_state();
+}
+
+#endif // USE_SYSTEM_RAND
+
// random2avg() returns same mean value as random2() but with a lower variance
// never use with rolls < 2 as that would be silly - use random2() instead {dlb}
int random2avg(int max, int rolls)
@@ -375,71 +552,6 @@ bool one_chance_in(int a_million)
return (random2(a_million) == 0);
} // end one_chance_in() - that's it? :P {dlb}
-// I got to thinking a bit more about how much people talk
-// about RNGs and RLs and also about the issue of performance
-// when it comes to Crawl's RNG ... turning to *Numerical
-// Recipies in C* (Chapter 7-4, page 298), I hit upon what
-// struck me as a fine solution.
-
-// You can read all the details about this function (pretty
-// much stolen shamelessly from NRinC) elsewhere, but having
-// tested it out myself I think it satisfies Crawl's incessant
-// need to decide things on a 50-50 flip of the coin. No call
-// to random2() required -- along with all that wonderful math
-// and type casting -- and only a single variable its pointer,
-// and some bitwise operations to randomly generate 1s and 0s!
-// No parameter passing, nothing. Too good to be true, but it
-// works as long as cfseed is not set to absolute zero when it
-// is initialized ... good for 2**n-1 random bits before the
-// pattern repeats (n = long's bitlength on your platform).
-// It also avoids problems with poor implementations of rand()
-// on some platforms in regards to low-order bits ... a big
-// problem if one is only looking for a 1 or a 0 with random2()!
-
-// Talk about a hard sell! Anyway, it returns bool, so please
-// use appropriately -- I set it to bool to prevent such
-// tomfoolery, as I think that pure RNG and quickly grabbing
-// either a value of 1 or 0 should be separated where possible
-// to lower overhead in Crawl ... at least until it assembles
-// itself into something a bit more orderly :P 16jan2000 {dlb}
-
-// NB(1): cfseed is defined atop stuff.cc
-// NB(2): IB(foo) and MASK are defined somewhere in defines.h
-// NB(3): the function assumes that cf_setseed() has been called
-// beforehand - the call is presently made in acr::initialise()
-// right after srandom() and srand() are called (note also
-// that cf_setseed() requires rand() - random2 returns int
-// but a long can't hurt there).
-bool coinflip(void)
-{
- extern unsigned long cfseed; // defined atop stuff.cc
- unsigned long *ptr_cfseed = &cfseed;
-
- if (*ptr_cfseed & IB18)
- {
- *ptr_cfseed = ((*ptr_cfseed ^ MASK) << 1) | IB1;
- return true;
- }
- else
- {
- *ptr_cfseed <<= 1;
- return false;
- }
-} // end coinflip()
-
-// cf_setseed should only be called but once in all of Crawl!!! {dlb}
-void cf_setseed(void)
-{
- extern unsigned long cfseed; // defined atop stuff.cc
- unsigned long *ptr_cfseed = &cfseed;
-
- do
- {
- // using rand() here makes these predictable -- bwr
- *ptr_cfseed = rand();
- }
- while (*ptr_cfseed == 0);
-}
// simple little function to quickly modify all three stats
// at once - does check for '0' modifiers to prevent needless
@@ -521,17 +633,23 @@ void canned_msg(unsigned char which_message)
// jmf: general helper (should be used all over in code)
// -- idea borrowed from Nethack
-bool yesno( const char *str, bool safe, bool clear_after )
+bool yesno( const char *str, bool safe, int safeanswer, bool clear_after )
{
unsigned char tmp;
+ interrupt_activity( AI_FORCE_INTERRUPT );
for (;;)
{
mpr(str, MSGCH_PROMPT);
tmp = (unsigned char) getch();
+ if ((tmp == ' ' || tmp == 27 || tmp == '\r' || tmp == '\n')
+ && safeanswer)
+ tmp = safeanswer;
+
if (Options.easy_confirm == CONFIRM_ALL_EASY
+ || tmp == safeanswer
|| (Options.easy_confirm == CONFIRM_SAFE_EASY && safe))
{
tmp = toupper( tmp );
diff --git a/trunk/source/stuff.h b/trunk/source/stuff.h
index 88fc11fb53..72c7d2dcf5 100644
--- a/trunk/source/stuff.h
+++ b/trunk/source/stuff.h
@@ -16,13 +16,21 @@
#include "externs.h"
-char *const make_time_string(time_t abs_time, char *const buff, int buff_size);
+char *const make_time_string(time_t abs_time, char *const buff, int buff_size, bool terse = false);
void set_redraw_status( unsigned long flags );
void tag_followers( void );
void untag_followers( void );
+void seed_rng(void);
+
+void seed_rng(long seed);
+
+void push_rng_state();
+
+void pop_rng_state();
+
// last updated 12may2000 {dlb}
/* ***********************************************************************
* called from: acr
@@ -47,6 +55,7 @@ bool one_chance_in(int a_million);
* *********************************************************************** */
int random2(int randmax);
+unsigned long random_int(void);
/* ***********************************************************************
* called from: xxx
@@ -125,7 +134,9 @@ void canned_msg(unsigned char which_message);
* called from: ability - acr - command - it_use3 - item_use - items -
* misc - ouch - religion - spl-book - spells4
* *********************************************************************** */
-bool yesno( const char * str, bool safe = true, bool clear_after = true );
+// If safeanswer is nonzero, it should be a lowercase letter.
+bool yesno( const char * str, bool safe = true, int safeanswer = 0,
+ bool clear_after = true );
// last updated 21may2000 {dlb}
diff --git a/trunk/source/tags.cc b/trunk/source/tags.cc
index b384159d7e..1aa6752a6b 100644
--- a/trunk/source/tags.cc
+++ b/trunk/source/tags.cc
@@ -129,7 +129,7 @@ static void tag_construct_ghost(struct tagHeader &th);
static void tag_read_ghost(struct tagHeader &th, char minorVersion);
// provide a wrapper for file writing, just in case.
-int write2(FILE * file, char *buffer, unsigned int count)
+int write2(FILE * file, const char *buffer, unsigned int count)
{
return fwrite(buffer, 1, count, file);
}
@@ -715,6 +715,9 @@ static void tag_construct_you(struct tagHeader &th)
marshallLong( th, you.real_time );
marshallLong( th, you.num_turns );
+
+ // you.magic_contamination 05/03/05
+ marshallShort(th, you.magic_contamination);
}
static void tag_construct_you_items(struct tagHeader &th)
@@ -733,6 +736,8 @@ static void tag_construct_you_items(struct tagHeader &th)
marshallLong(th,you.inv[i].flags);
marshallShort(th,you.inv[i].quantity);
marshallShort(th,you.inv[i].plus2);
+ marshallShort(th, you.inv[i].orig_place);
+ marshallShort(th, you.inv[i].orig_monnum);
}
// item descrip for each type & subtype
@@ -1014,6 +1019,12 @@ static void tag_read_you(struct tagHeader &th, char minorVersion)
you.real_time = -1;
you.num_turns = -1;
}
+
+ // you.magic_contamination 05/03/05
+ if (minorVersion >= 3)
+ {
+ you.magic_contamination = unmarshallShort(th);
+ }
}
static void tag_convert_to_4_3_item( item_def &item )
@@ -1254,6 +1265,7 @@ static void tag_read_you_items(struct tagHeader &th, char minorVersion)
count_c = unmarshallByte(th);
for (i = 0; i < count_c; ++i)
{
+ you.inv[i].orig_monnum = you.inv[i].orig_place = 0;
if (minorVersion < 1)
{
you.inv[i].base_type = (unsigned char) unmarshallByte(th);
@@ -1277,12 +1289,19 @@ static void tag_read_you_items(struct tagHeader &th, char minorVersion)
you.inv[i].flags = (unsigned long) unmarshallLong(th);
you.inv[i].quantity = unmarshallShort(th);
you.inv[i].plus2 = unmarshallShort(th);
+
+ if (minorVersion >= 4)
+ {
+ you.inv[i].orig_place = unmarshallShort(th);
+ you.inv[i].orig_monnum = unmarshallShort(th);
+ }
}
// these never need to be saved for items in the inventory -- bwr
you.inv[i].x = -1;
you.inv[i].y = -1;
you.inv[i].link = i;
+ you.inv[i].slot = index_to_letter(i);
}
// item descrip for each type & subtype
@@ -1398,7 +1417,7 @@ static void tag_construct_level(struct tagHeader &th)
for (count_y = 0; count_y < GYM; count_y++)
{
marshallByte(th, grd[count_x][count_y]);
- marshallByte(th, env.map[count_x][count_y]);
+ marshallShort(th, env.map[count_x][count_y]);
marshallByte(th, env.cgrid[count_x][count_y]);
}
}
@@ -1461,6 +1480,11 @@ static void tag_construct_level_items(struct tagHeader &th)
marshallShort(th, mitm[i].link); // unused
marshallShort(th, igrd[mitm[i].x][mitm[i].y]); // unused
+
+ marshallByte(th, mitm[i].slot);
+
+ marshallShort(th, mitm[i].orig_place);
+ marshallShort(th, mitm[i].orig_monnum);
}
}
@@ -1539,9 +1563,14 @@ static void tag_read_level( struct tagHeader &th, char minorVersion )
for (j = 0; j < gy; j++)
{
grd[i][j] = unmarshallByte(th);
- env.map[i][j] = unmarshallByte(th);
- if (env.map[i][j] == 201) // what is this??
- env.map[i][j] = 239;
+
+ if (minorVersion < 8)
+ env.map[i][j] = (unsigned char) unmarshallByte(th);
+ else
+ env.map[i][j] = (unsigned short) unmarshallShort(th);
+
+ if ((env.map[i][j] & 0xFF) == 201) // what is this??
+ env.map[i][j] = (env.map[i][j] & 0xFF00U) | 239;
mgrd[i][j] = NON_MONSTER;
env.cgrid[i][j] = unmarshallByte(th);
@@ -1638,6 +1667,22 @@ static void tag_read_level_items(struct tagHeader &th, char minorVersion)
unmarshallShort(th); // mitm[].link -- unused
unmarshallShort(th); // igrd[mitm[i].x][mitm[i].y] -- unused
+
+ if (minorVersion >= 6)
+ {
+ mitm[i].slot = unmarshallByte(th);
+ }
+
+ if (minorVersion >= 7)
+ {
+ mitm[i].orig_place = unmarshallShort(th);
+ mitm[i].orig_monnum = unmarshallShort(th);
+ }
+ else
+ {
+ mitm[i].orig_place = 0;
+ mitm[i].orig_monnum = 0;
+ }
}
}
@@ -1664,7 +1709,8 @@ static void tag_read_level_monsters(struct tagHeader &th, char minorVersion)
menv[i].evasion = unmarshallByte(th);
menv[i].hit_dice = unmarshallByte(th);
menv[i].speed = unmarshallByte(th);
- menv[i].speed_increment = unmarshallByte(th);
+ // Avoid sign extension when loading files (Elethiomel's hang)
+ menv[i].speed_increment = (unsigned char) unmarshallByte(th);
menv[i].behaviour = unmarshallByte(th);
menv[i].x = unmarshallByte(th);
menv[i].y = unmarshallByte(th);
diff --git a/trunk/source/tags.h b/trunk/source/tags.h
index 9b7a0e85e9..7c84709ebf 100644
--- a/trunk/source/tags.h
+++ b/trunk/source/tags.h
@@ -18,7 +18,7 @@
/* ***********************************************************************
* called from: files tags
* *********************************************************************** */
-int write2(FILE * file, char *buffer, unsigned int count);
+int write2(FILE * file, const char *buffer, unsigned int count);
// last updated 22jan2001 {gdl}
diff --git a/trunk/source/travel.cc b/trunk/source/travel.cc
new file mode 100755
index 0000000000..d65a852b9c
--- /dev/null
+++ b/trunk/source/travel.cc
@@ -0,0 +1,2923 @@
+/*
+ * File: travel.cc
+ * Summary: Travel stuff
+ * Written by: Darshan Shaligram
+ *
+ * Known issues:
+ * Hardcoded dungeon features all over the place - this thing is a devil to
+ * refactor.
+ */
+#include "AppHdr.h"
+#include "files.h"
+#include "FixAry.h"
+#include "clua.h"
+#include "mon-util.h"
+#include "player.h"
+#include "stash.h"
+#include "stuff.h"
+#include "travel.h"
+#include "view.h"
+#include <stdarg.h>
+#include <ctype.h>
+#include <stdio.h>
+
+#ifdef DOS
+#include <dos.h>
+#endif
+
+#define TC_MAJOR_VERSION ((unsigned char) 4)
+#define TC_MINOR_VERSION ((unsigned char) 4)
+
+enum IntertravelDestination
+{
+ // Go down a level
+ ID_DOWN = -100,
+
+ // Go up a level
+ ID_UP = -99,
+
+ // Repeat last travel
+ ID_REPEAT = -101,
+
+ // Cancel interlevel travel
+ ID_CANCEL = -1000,
+};
+
+TravelCache travel_cache;
+
+// Tracks the distance between the target location on the target level and the
+// stairs on the level.
+static std::vector<stair_info> curr_stairs;
+
+// Squares that are not safe to travel to on the current level.
+static std::vector<coord_def> curr_excludes;
+
+// This is where we last tried to take a stair during interlevel travel.
+// Note that last_stair.depth should be set to -1 before initiating interlevel
+// travel.
+static level_id last_stair;
+
+// Where travel wants to get to.
+static level_pos travel_target;
+
+// The place in the Vestibule of Hell where all portals to Hell land.
+static level_pos travel_hell_entry;
+
+static bool traps_inited = false;
+
+// TODO: Do we need this or would a different header be better?
+inline int sgn(int x)
+{
+ return x < 0? -1 : (x > 0);
+}
+
+/* These are defined in view.cc. BWR says these may become obsolete in next
+ * release? */
+extern unsigned char (*mapch) (unsigned char);
+extern unsigned char (*mapch2) (unsigned char);
+extern unsigned char mapchar(unsigned char ldfk);
+extern unsigned char mapchar2(unsigned char ldfk);
+
+
+// Array of points on the map, each value being the distance the character
+// would have to travel to get there. Negative distances imply that the point
+// is a) a trap or hostile terrain or b) only reachable by crossing a trap or
+// hostile terrain.
+short point_distance[GXM][GYM];
+
+unsigned char curr_waypoints[GXM][GYM];
+
+signed char curr_traps[GXM][GYM];
+
+static FixedArray< unsigned short, GXM, GYM > mapshadow;
+
+// Clockwise, around the compass from north (same order as enum RUN_DIR)
+// Copied from acr.cc
+static const struct coord_def Compass[8] =
+{
+ { 0, -1 }, { 1, -1 }, { 1, 0 }, { 1, 1 },
+ { 0, 1 }, { -1, 1 }, { -1, 0 }, { -1, -1 },
+};
+
+#define TRAVERSABLE 1
+#define IMPASSABLE 0
+#define FORBIDDEN -1
+
+// Map of terrain types that are traversable.
+static signed char traversable_terrain[256];
+
+static int trans_negotiate_stairs();
+static int find_transtravel_square(const level_pos &pos, bool verbose = true);
+
+static bool loadlev_populate_stair_distances(const level_pos &target);
+static void populate_stair_distances(const level_pos &target);
+
+// Determines whether the player has seen this square, given the user-visible
+// character.
+//
+// The player is assumed to have seen the square if:
+// a. The square is mapped (the env map char is not zero)
+// b. The square was *not* magic-mapped.
+//
+bool is_player_mapped(unsigned char envch)
+{
+ // Note that we're relying here on mapch(DNGN_FLOOR) != mapch2(DNGN_FLOOR)
+ // and that no *other* dungeon feature renders as mapch(DNGN_FLOOR).
+ // The check for a ~ is to ensure explore stops for items turned up by
+ // detect items.
+ return envch && envch != mapch(DNGN_FLOOR) && envch != '~';
+}
+
+inline bool is_trap(unsigned char grid)
+{
+ return (grid == DNGN_TRAP_MECHANICAL || grid == DNGN_TRAP_MAGICAL
+ || grid == DNGN_TRAP_III);
+}
+
+// Returns true if there is a known trap at (x,y). Returns false for non-trap
+// squares as also for undiscovered traps.
+//
+inline bool is_trap(int x, int y)
+{
+ return is_trap( grd[x][y] );
+}
+
+// Returns true if this feature takes extra time to cross.
+inline int feature_traverse_cost(unsigned char feature)
+{
+ return (feature == DNGN_SHALLOW_WATER || feature == DNGN_CLOSED_DOOR? 2 :
+ is_trap(feature) ? 3 : 1);
+}
+
+// Returns true if the dungeon feature supplied is an altar.
+inline bool is_altar(unsigned char grid)
+{
+ return grid >= DNGN_ALTAR_ZIN && grid <= DNGN_ALTAR_ELYVILON;
+}
+
+inline bool is_altar(const coord_def &c)
+{
+ return is_altar(grd[c.x][c.y]);
+}
+
+inline bool is_player_altar(unsigned char grid)
+{
+ // An ugly hack, but that's what religion.cc does.
+ return you.religion
+ && grid == DNGN_ALTAR_ZIN - 1 + you.religion;
+}
+
+inline bool is_player_altar(const coord_def &c)
+{
+ return is_player_altar(grd[c.x][c.y]);
+}
+
+// Copies FixedArray src to FixedArray dest.
+//
+inline void copy(const FixedArray<unsigned short, GXM, GYM> &src,
+ FixedArray<unsigned short, GXM, GYM> &dest)
+{
+ dest = src;
+}
+
+#ifdef CLUA_BINDINGS
+static void init_traps()
+{
+ memset(curr_traps, -1, sizeof curr_traps);
+ for (int i = 0; i < MAX_TRAPS; ++i)
+ {
+ int x = env.trap[i].x,
+ y = env.trap[i].y;
+ if (x > 0 && x < GXM && y > 0 && y < GYM)
+ curr_traps[x][y] = i;
+ }
+ traps_inited = true;
+}
+
+static const char *trap_names[] =
+{
+ "dart", "arrow", "spear", "axe",
+ "teleport", "amnesia", "blade",
+ "bolt", "zot", "needle",
+};
+
+static const char *trap_name(int x, int y)
+{
+ if (!traps_inited)
+ init_traps();
+
+ const int ti = curr_traps[x][y];
+ if (ti != -1)
+ {
+ int type = env.trap[ti].type;
+ if (type >= 0 && type < NUM_TRAPS)
+ return (trap_names[type]);
+ }
+ return ("");
+}
+#endif
+
+/*
+ * Returns true if the character can cross this dungeon feature.
+ */
+inline bool is_traversable(unsigned char grid)
+{
+ return traversable_terrain[(int) grid] == TRAVERSABLE;
+}
+
+static bool is_excluded(int x, int y)
+{
+ for (int i = 0, count = curr_excludes.size(); i < count; ++i)
+ {
+ const coord_def &c = curr_excludes[i];
+ int dx = c.x - x,
+ dy = c.y - y;
+ if (dx * dx + dy * dy <= Options.travel_exclude_radius2)
+ return true;
+ }
+ return false;
+}
+
+static bool is_exclude_root(int x, int y)
+{
+ for (int i = 0, count = curr_excludes.size(); i < count; ++i)
+ {
+ const coord_def &c = curr_excludes[i];
+ if (c.x == x && c.y == y)
+ return true;
+ }
+ return false;
+}
+
+const char *run_mode_name(int runmode)
+{
+ return runmode == RUN_TRAVEL? "travel" :
+ runmode == RUN_INTERLEVEL? "intertravel" :
+ runmode == RUN_EXPLORE? "explore" :
+ runmode > 0? "run" :
+ "";
+}
+
+unsigned char is_waypoint(int x, int y)
+{
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS
+ || you.level_type == LEVEL_PANDEMONIUM)
+ return 0;
+ return curr_waypoints[x][y];
+}
+
+#ifdef STASH_TRACKING
+inline bool is_stash(LevelStashes *ls, int x, int y)
+{
+ if (!ls)
+ return (false);
+ Stash *s = ls->find_stash(x, y);
+ return s && s->enabled;
+}
+#endif
+
+void clear_excludes()
+{
+ // Sanity checks
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
+ return;
+
+ curr_excludes.clear();
+
+ if (can_travel_interlevel())
+ {
+ LevelInfo &li = travel_cache.get_level_info(
+ level_id::get_current_level_id());
+ li.update();
+ }
+}
+
+void toggle_exclude(int x, int y)
+{
+ // Sanity checks
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
+ return;
+
+ if (x <= 0 || x >= GXM || y <= 0 || y >= GYM) return;
+ if (!env.map[x - 1][y - 1]) return;
+
+ if (is_exclude_root(x, y))
+ {
+ for (int i = 0, count = curr_excludes.size(); i < count; ++i)
+ {
+ const coord_def &c = curr_excludes[i];
+ if (c.x == x && c.y == y)
+ {
+ curr_excludes.erase( curr_excludes.begin() + i );
+ break ;
+ }
+ }
+ }
+ else
+ {
+ coord_def c = { x, y };
+ curr_excludes.push_back(c);
+ }
+
+ if (can_travel_interlevel())
+ {
+ LevelInfo &li = travel_cache.get_level_info(
+ level_id::get_current_level_id());
+ li.update();
+ }
+}
+
+void update_excludes()
+{
+ // Sanity checks
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
+ return;
+ for (int i = curr_excludes.size() - 1; i >= 0; --i)
+ {
+ int x = curr_excludes[i].x,
+ y = curr_excludes[i].y;
+ if (!env.map[x - 1][y - 1])
+ curr_excludes.erase( curr_excludes.begin() + i );
+ }
+}
+
+void forget_square(int x, int y)
+{
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS)
+ return;
+
+ if (is_exclude_root(x, y))
+ toggle_exclude(x, y);
+}
+
+/*
+ * Returns true if the square at (x,y) is a dungeon feature the character
+ * can't (under normal circumstances) safely cross.
+ *
+ * Note: is_reseedable can return true for dungeon features that is_traversable
+ * also returns true for. This is okay, because is_traversable always
+ * takes precedence over is_reseedable. is_reseedable is used only to
+ * decide which squares to reseed from when flood-filling outwards to
+ * colour the level map. It does not affect pathing of actual
+ * travel/explore.
+ */
+static bool is_reseedable(int x, int y)
+{
+ if (is_excluded(x, y))
+ return true;
+ unsigned char grid = grd[x][y];
+ return (grid == DNGN_DEEP_WATER || grid == DNGN_SHALLOW_WATER ||
+ grid == DNGN_LAVA || is_trap(x, y));
+}
+
+/*
+ * Returns true if the square at (x,y) is okay to travel over. If ignore_hostile
+ * is true, returns true even for dungeon features the character can normally
+ * not cross safely (deep water, lava, traps).
+ */
+static bool is_travel_ok(int x, int y, bool ignore_hostile)
+{
+ unsigned char grid = grd[x][y];
+
+ unsigned char envc = (unsigned char) env.map[x - 1][y - 1];
+ if (!envc) return false;
+
+ // Special-case secret doors so that we don't run into awkwardness when
+ // a monster opens a secret door without the hero seeing it, but the travel
+ // code paths through the secret door because it looks at the actual grid,
+ // rather than the env overmap. Hopefully there won't be any more such
+ // cases.
+ if (envc == mapch2(DNGN_SECRET_DOOR)) return false;
+
+ unsigned char mon = mgrd[x][y];
+ if (mon != NON_MONSTER)
+ {
+ // Kludge warning: navigating around zero-exp beasties uses knowledge
+ // that the player may not have (the player may not
+ // know that there's a plant at any given (x,y), but we
+ // know, because we're looking directly at the grid).
+ // Arguably the utility of this feature is greater than
+ // the information we're giving the player for free.
+ // Navigate around plants and fungi. Yet another tasty hack.
+ if (player_monster_visible(&menv[mon]) &&
+ mons_flag( menv[mon].type, M_NO_EXP_GAIN ))
+ {
+ extern short point_distance[GXM][GYM];
+
+ // We have to set the point_distance array if the level map is
+ // to be properly coloured. The caller isn't going to do it because
+ // we say this square is inaccessible, so in a horrible hack, we
+ // do it ourselves. Ecch.
+ point_distance[x][y] = ignore_hostile? -42 : 42;
+ return false;
+ }
+ }
+
+ // If 'ignore_hostile' is true, we're ignoring hazards that can be
+ // navigated over if the player is willing to take damage, or levitate.
+ if (ignore_hostile && is_reseedable(x, y))
+ return true;
+
+ return (is_traversable(grid)
+#ifdef CLUA_BINDINGS
+ ||
+ (is_trap(x, y) &&
+ clua.callbooleanfn(false, "ch_cross_trap",
+ "s", trap_name(x, y)))
+#endif
+ )
+ && !is_excluded(x, y);
+}
+
+// Returns true if the location at (x,y) is monster-free and contains no clouds.
+static bool is_safe(int x, int y)
+{
+ unsigned char mon = mgrd[x][y];
+ if (mon != NON_MONSTER)
+ {
+ // If this is an invisible critter, say we're safe to get here, but
+ // turn off travel - the result should be that the player bashes into
+ // the monster and stops travelling right there. Same treatment applies
+ // to mimics.
+ if (!player_monster_visible(&menv[mon]) ||
+ mons_is_mimic( menv[mon].type ))
+ {
+ you.running = 0;
+ return true;
+ }
+
+ // Stop before wasting energy on plants and fungi.
+ if (mons_flag( menv[mon].type, M_NO_EXP_GAIN ))
+ return false;
+
+ // If this is any *other* monster, it'll be visible and
+ // a) Friendly, in which case we'll displace it, no problem.
+ // b) Unfriendly, in which case we're in deep trouble, since travel
+ // should have been aborted already by the checks in view.cc.
+ }
+ const char cloud = env.cgrid[x][y];
+ if (cloud == EMPTY_CLOUD)
+ return true;
+
+ // We can also safely run through smoke.
+ const char cloud_type = env.cloud[ cloud ].type;
+ return cloud_type == CLOUD_GREY_SMOKE ||
+ cloud_type == CLOUD_GREY_SMOKE_MON ||
+ cloud_type == CLOUD_BLUE_SMOKE ||
+ cloud_type == CLOUD_BLUE_SMOKE_MON ||
+ cloud_type == CLOUD_PURP_SMOKE ||
+ cloud_type == CLOUD_PURP_SMOKE_MON ||
+ cloud_type == CLOUD_BLACK_SMOKE ||
+ cloud_type == CLOUD_BLACK_SMOKE_MON;
+}
+
+static bool player_is_permalevitating()
+{
+ return you.levitation > 1 &&
+ ((you.species == SP_KENKU && you.experience_level >= 15)
+ || player_equip_ego_type( EQ_BOOTS, SPARM_LEVITATION ));
+}
+
+static void set_pass_feature(unsigned char grid, signed char pass)
+{
+ if (traversable_terrain[(unsigned) grid] != FORBIDDEN)
+ traversable_terrain[(unsigned) grid] = pass;
+}
+
+/*
+ * Sets traversable terrain based on the character's role and whether or not he
+ * has permanent levitation
+ */
+static void init_terrain_check()
+{
+ // Merfolk get deep water.
+ signed char water = you.species == SP_MERFOLK? TRAVERSABLE : IMPASSABLE;
+ // If the player has overridden deep water already, we'll respect that.
+ set_pass_feature(DNGN_DEEP_WATER, water);
+
+ // Permanently levitating players can cross most hostile terrain.
+ signed char trav = player_is_permalevitating()?
+ TRAVERSABLE : IMPASSABLE;
+ if (you.species != SP_MERFOLK)
+ set_pass_feature(DNGN_DEEP_WATER, trav);
+ set_pass_feature(DNGN_LAVA, trav);
+ set_pass_feature(DNGN_TRAP_MECHANICAL, trav);
+}
+
+void travel_init_new_level()
+{
+ // Zero out last travel coords
+ you.run_x = you.run_y = 0;
+ you.travel_x = you.travel_y = 0;
+
+ traps_inited = false;
+ curr_excludes.clear();
+ travel_cache.set_level_excludes();
+ travel_cache.update_waypoints();
+}
+
+/*
+ * Sets up travel-related stuff.
+ */
+void initialise_travel()
+{
+ // Need a better way to do this. :-(
+ traversable_terrain[DNGN_FLOOR] =
+ traversable_terrain[DNGN_ENTER_HELL] =
+ traversable_terrain[DNGN_OPEN_DOOR] =
+ traversable_terrain[DNGN_BRANCH_STAIRS] =
+ traversable_terrain[DNGN_UNDISCOVERED_TRAP] =
+ traversable_terrain[DNGN_ENTER_SHOP] =
+ traversable_terrain[DNGN_ENTER_LABYRINTH] =
+ traversable_terrain[DNGN_STONE_STAIRS_DOWN_I] =
+ traversable_terrain[DNGN_STONE_STAIRS_DOWN_II] =
+ traversable_terrain[DNGN_STONE_STAIRS_DOWN_III] =
+ traversable_terrain[DNGN_ROCK_STAIRS_DOWN] =
+ traversable_terrain[DNGN_STONE_STAIRS_UP_I] =
+ traversable_terrain[DNGN_STONE_STAIRS_UP_II] =
+ traversable_terrain[DNGN_STONE_STAIRS_UP_III] =
+ traversable_terrain[DNGN_ROCK_STAIRS_UP] =
+ traversable_terrain[DNGN_ENTER_DIS] =
+ traversable_terrain[DNGN_ENTER_GEHENNA] =
+ traversable_terrain[DNGN_ENTER_COCYTUS] =
+ traversable_terrain[DNGN_ENTER_TARTARUS] =
+ traversable_terrain[DNGN_ENTER_ABYSS] =
+ traversable_terrain[DNGN_EXIT_ABYSS] =
+ traversable_terrain[DNGN_STONE_ARCH] =
+ traversable_terrain[DNGN_ENTER_PANDEMONIUM] =
+ traversable_terrain[DNGN_EXIT_PANDEMONIUM] =
+ traversable_terrain[DNGN_TRANSIT_PANDEMONIUM] =
+ traversable_terrain[DNGN_ENTER_ORCISH_MINES] =
+ traversable_terrain[DNGN_ENTER_HIVE] =
+ traversable_terrain[DNGN_ENTER_LAIR] =
+ traversable_terrain[DNGN_ENTER_SLIME_PITS] =
+ traversable_terrain[DNGN_ENTER_VAULTS] =
+ traversable_terrain[DNGN_ENTER_CRYPT] =
+ traversable_terrain[DNGN_ENTER_HALL_OF_BLADES] =
+ traversable_terrain[DNGN_ENTER_ZOT] =
+ traversable_terrain[DNGN_ENTER_TEMPLE] =
+ traversable_terrain[DNGN_ENTER_SNAKE_PIT] =
+ traversable_terrain[DNGN_ENTER_ELVEN_HALLS] =
+ traversable_terrain[DNGN_ENTER_TOMB] =
+ traversable_terrain[DNGN_ENTER_SWAMP] =
+ traversable_terrain[DNGN_RETURN_FROM_ORCISH_MINES] =
+ traversable_terrain[DNGN_RETURN_FROM_HIVE] =
+ traversable_terrain[DNGN_RETURN_FROM_LAIR] =
+ traversable_terrain[DNGN_RETURN_FROM_SLIME_PITS] =
+ traversable_terrain[DNGN_RETURN_FROM_VAULTS] =
+ traversable_terrain[DNGN_RETURN_FROM_CRYPT] =
+ traversable_terrain[DNGN_RETURN_FROM_HALL_OF_BLADES] =
+ traversable_terrain[DNGN_RETURN_FROM_ZOT] =
+ traversable_terrain[DNGN_RETURN_FROM_TEMPLE] =
+ traversable_terrain[DNGN_RETURN_FROM_SNAKE_PIT] =
+ traversable_terrain[DNGN_RETURN_FROM_ELVEN_HALLS] =
+ traversable_terrain[DNGN_RETURN_FROM_TOMB] =
+ traversable_terrain[DNGN_RETURN_FROM_SWAMP] =
+ traversable_terrain[DNGN_ALTAR_ZIN] =
+ traversable_terrain[DNGN_ALTAR_SHINING_ONE] =
+ traversable_terrain[DNGN_ALTAR_KIKUBAAQUDGHA] =
+ traversable_terrain[DNGN_ALTAR_YREDELEMNUL] =
+ traversable_terrain[DNGN_ALTAR_XOM] =
+ traversable_terrain[DNGN_ALTAR_VEHUMET] =
+ traversable_terrain[DNGN_ALTAR_OKAWARU] =
+ traversable_terrain[DNGN_ALTAR_MAKHLEB] =
+ traversable_terrain[DNGN_ALTAR_SIF_MUNA] =
+ traversable_terrain[DNGN_ALTAR_TROG] =
+ traversable_terrain[DNGN_ALTAR_NEMELEX_XOBEH] =
+ traversable_terrain[DNGN_ALTAR_ELYVILON] =
+ traversable_terrain[DNGN_BLUE_FOUNTAIN] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_I] =
+ traversable_terrain[DNGN_SPARKLING_FOUNTAIN] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_II] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_III] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_IV] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_V] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_VI] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_VII] =
+ traversable_terrain[DNGN_DRY_FOUNTAIN_VIII] =
+ traversable_terrain[DNGN_PERMADRY_FOUNTAIN] =
+ traversable_terrain[DNGN_CLOSED_DOOR] =
+ traversable_terrain[DNGN_SHALLOW_WATER] =
+ TRAVERSABLE;
+}
+
+/*
+ * Given a dungeon feature description, returns the feature number. This is a
+ * crude hack and currently recognises only (deep/shallow) water.
+ *
+ * Returns -1 if the feature named is not recognised, else returns the feature
+ * number (guaranteed to be 0-255).
+ */
+int get_feature_type(const std::string &feature)
+{
+ if (feature.find("deep water") != std::string::npos)
+ return DNGN_DEEP_WATER;
+ if (feature.find("shallow water") != std::string::npos)
+ return DNGN_SHALLOW_WATER;
+ return -1;
+}
+
+/*
+ * Given a feature description, prevents travel to locations of that feature
+ * type.
+ */
+void prevent_travel_to(const std::string &feature)
+{
+ int feature_type = get_feature_type(feature);
+ if (feature_type != -1)
+ traversable_terrain[feature_type] = FORBIDDEN;
+}
+
+bool is_stair(unsigned gridc)
+{
+ return (is_travelable_stair(gridc)
+ || gridc == DNGN_ENTER_ABYSS
+ || gridc == DNGN_ENTER_LABYRINTH
+ || gridc == DNGN_ENTER_PANDEMONIUM
+ || gridc == DNGN_EXIT_PANDEMONIUM
+ || gridc == DNGN_TRANSIT_PANDEMONIUM);
+}
+
+/*
+ * Returns true if the given dungeon feature can be considered a stair.
+ */
+bool is_travelable_stair(unsigned gridc)
+{
+ switch (gridc)
+ {
+ case DNGN_ENTER_HELL:
+ case DNGN_STONE_STAIRS_DOWN_I:
+ case DNGN_STONE_STAIRS_DOWN_II:
+ case DNGN_STONE_STAIRS_DOWN_III:
+ case DNGN_ROCK_STAIRS_DOWN:
+ case DNGN_STONE_STAIRS_UP_I:
+ case DNGN_STONE_STAIRS_UP_II:
+ case DNGN_STONE_STAIRS_UP_III:
+ case DNGN_ROCK_STAIRS_UP:
+ case DNGN_ENTER_DIS:
+ case DNGN_ENTER_GEHENNA:
+ case DNGN_ENTER_COCYTUS:
+ case DNGN_ENTER_TARTARUS:
+ case DNGN_ENTER_ORCISH_MINES:
+ case DNGN_ENTER_HIVE:
+ case DNGN_ENTER_LAIR:
+ case DNGN_ENTER_SLIME_PITS:
+ case DNGN_ENTER_VAULTS:
+ case DNGN_ENTER_CRYPT:
+ case DNGN_ENTER_HALL_OF_BLADES:
+ case DNGN_ENTER_ZOT:
+ case DNGN_ENTER_TEMPLE:
+ case DNGN_ENTER_SNAKE_PIT:
+ case DNGN_ENTER_ELVEN_HALLS:
+ case DNGN_ENTER_TOMB:
+ case DNGN_ENTER_SWAMP:
+ case DNGN_RETURN_FROM_ORCISH_MINES:
+ case DNGN_RETURN_FROM_HIVE:
+ case DNGN_RETURN_FROM_LAIR:
+ case DNGN_RETURN_FROM_SLIME_PITS:
+ case DNGN_RETURN_FROM_VAULTS:
+ case DNGN_RETURN_FROM_CRYPT:
+ case DNGN_RETURN_FROM_HALL_OF_BLADES:
+ case DNGN_RETURN_FROM_ZOT:
+ case DNGN_RETURN_FROM_TEMPLE:
+ case DNGN_RETURN_FROM_SNAKE_PIT:
+ case DNGN_RETURN_FROM_ELVEN_HALLS:
+ case DNGN_RETURN_FROM_TOMB:
+ case DNGN_RETURN_FROM_SWAMP:
+ return true;
+ default:
+ return false;
+ }
+}
+
+#define ES_item (Options.explore_stop & ES_ITEM)
+#define ES_shop (Options.explore_stop & ES_SHOP)
+#define ES_stair (Options.explore_stop & ES_STAIR)
+#define ES_altar (Options.explore_stop & ES_ALTAR)
+
+/*
+ * Given a square that has just become visible during explore, returns true
+ * if the player might consider the square worth stopping explore for.
+ */
+static bool is_interesting_square(int x, int y)
+{
+ if (ES_item && igrd[x + 1][y + 1] != NON_ITEM)
+ return true;
+
+ unsigned char grid = grd[x + 1][y + 1];
+ return (ES_shop && grid == DNGN_ENTER_SHOP)
+ || (ES_stair && is_stair(grid))
+ || (ES_altar && is_altar(grid)
+ && you.where_are_you != BRANCH_ECUMENICAL_TEMPLE);
+}
+
+static void userdef_run_stoprunning_hook(void)
+{
+#ifdef CLUA_BINDINGS
+ if (you.running)
+ clua.callfn("ch_stop_running", "s", run_mode_name(you.running));
+#endif
+}
+
+static void userdef_run_startrunning_hook(void)
+{
+#ifdef CLUA_BINDINGS
+ if (you.running)
+ clua.callfn("ch_start_running", "s", run_mode_name(you.running));
+#endif
+}
+
+/*
+ * Stops shift+running and all forms of travel.
+ */
+void stop_running(void)
+{
+ userdef_run_stoprunning_hook();
+ you.running = 0;
+}
+
+void start_running(void)
+{
+ userdef_run_startrunning_hook();
+}
+
+/*
+ * Top-level travel control (called from input() in acr.cc).
+ *
+ * travel() is responsible for making the individual moves that constitute
+ * (interlevel) travel and explore and deciding when travel and explore
+ * end.
+ *
+ * Don't call travel() if you.running >= 0.
+ */
+void travel(int *keyin, char *move_x, char *move_y)
+{
+ *keyin = 128;
+
+ // Abort travel/explore if you're confused or a key was pressed.
+ if (kbhit() || you.conf)
+ {
+ stop_running();
+ *keyin = 0;
+ if (Options.travel_delay == -1)
+ redraw_screen();
+ return ;
+ }
+
+ if (Options.explore_stop && you.running == RUN_EXPLORE)
+ {
+ // Scan through the shadow map, compare it with the actual map, and if
+ // there are any squares of the shadow map that have just been
+ // discovered and contain an item, or have an interesting dungeon
+ // feature, stop exploring.
+ for (int y = 0; y < GYM - 1; ++y)
+ {
+ for (int x = 0; x < GXM - 1; ++x)
+ {
+ if (!is_player_mapped(mapshadow[x][y])
+ && is_player_mapped((unsigned char) env.map[x][y])
+ && is_interesting_square(x, y))
+ {
+ stop_running();
+ y = GYM;
+ break;
+ }
+ }
+ }
+ copy(env.map, mapshadow);
+ }
+
+ if (you.running == RUN_EXPLORE)
+ {
+ // Exploring
+ you.run_x = 0;
+ find_travel_pos(you.x_pos, you.y_pos, NULL, NULL);
+ // No place to go?
+ if (!you.run_x)
+ stop_running();
+ }
+
+ if (you.running == RUN_INTERLEVEL && !you.run_x)
+ {
+ // Interlevel travel. Since you.run_x is zero, we've either just
+ // initiated travel, or we've just climbed or descended a staircase,
+ // and we need to figure out where to travel to next.
+ if (!find_transtravel_square(travel_target) || !you.run_x)
+ stop_running();
+ }
+
+ if (you.running < 0)
+ {
+ // Remember what run-mode we were in so that we can resume explore/
+ // interlevel travel correctly.
+ int runmode = you.running;
+
+ // Get the next step to make. If the travel command can't find a route,
+ // we turn off travel (find_travel_pos does that automatically).
+ find_travel_pos(you.x_pos, you.y_pos, move_x, move_y);
+
+ if (!*move_x && !*move_y)
+ {
+ // If we've reached the square we were traveling towards, travel
+ // should stop if this is simple travel. If we're exploring, we
+ // should continue doing so (explore has its own end condition
+ // upstairs); if we're traveling between levels and we've reached
+ // our travel target, we're on a staircase and should take it.
+ if (you.x_pos == you.run_x && you.y_pos == you.run_y)
+ {
+ if (runmode == RUN_EXPLORE)
+ you.running = RUN_EXPLORE; // Turn explore back on
+
+ // For interlevel travel, we'll want to take the stairs unless
+ // the interlevel travel specified a destination square and
+ // we've reached that destination square.
+ else if (runmode == RUN_INTERLEVEL
+ && (travel_target.pos.x != you.x_pos
+ || travel_target.pos.y != you.y_pos
+ || travel_target.id !=
+ level_id::get_current_level_id()))
+ {
+ if (last_stair.depth != -1
+ && last_stair == level_id::get_current_level_id())
+ {
+ // We're trying to take the same stairs again. Baaad.
+
+ // We don't directly call stop_running() because
+ // you.running is probably 0, and stop_running() won't
+ // notify Lua hooks if you.running == 0.
+ you.running = runmode;
+ stop_running();
+ return;
+ }
+ you.running = RUN_INTERLEVEL;
+ *keyin = trans_negotiate_stairs();
+
+ // If, for some reason, we fail to use the stairs, we
+ // need to make sure we don't go into an infinite loop
+ // trying to take it again and again. We'll check
+ // last_stair before attempting to take stairs again.
+ last_stair = level_id::get_current_level_id();
+
+ // This is important, else we'll probably stop traveling
+ // the moment we clear the stairs. That's because the
+ // (run_x, run_y) destination will no longer be valid on
+ // the new level. Setting run_x to zero forces us to
+ // recalculate our travel target next turn (see previous
+ // if block).
+ you.run_x = you.run_y = 0;
+ }
+ else
+ {
+ you.running = runmode;
+ stop_running();
+ }
+ }
+ else
+ {
+ you.running = runmode;
+ stop_running();
+ }
+ }
+ else if (Options.travel_delay > 0)
+ delay(Options.travel_delay);
+ }
+
+ if (!you.running && Options.travel_delay == -1)
+ redraw_screen();
+}
+
+/*
+ * The travel algorithm is based on the NetHack travel code written by Warwick
+ * Allison - used with his permission.
+ */
+void find_travel_pos(int youx, int youy,
+ char *move_x, char *move_y,
+ std::vector<coord_def>* features)
+{
+ init_terrain_check();
+
+ int start_x = you.run_x, start_y = you.run_y;
+ int dest_x = youx, dest_y = youy;
+ bool floodout = false;
+#ifdef STASH_TRACKING
+ LevelStashes *lev = features? stashes.find_current_level() : NULL;
+#endif
+
+ // Normally we start from the destination and floodfill outwards, looking
+ // for the character's current position. If we're merely trying to populate
+ // the point_distance array (or exploring), we'll want to start from the
+ // character's current position and fill outwards
+ if (!move_x || !move_y)
+ {
+ start_x = youx;
+ start_y = youy;
+
+ dest_x = dest_y = -1;
+
+ floodout = true;
+ }
+
+ // Abort run if we're trying to go someplace evil
+ if (dest_x != -1 && !is_travel_ok(start_x, start_y, false) &&
+ !is_trap(start_x, start_y))
+ {
+ you.running = 0;
+ return ;
+ }
+
+ // Abort run if we're going nowhere.
+ if (start_x == dest_x && start_y == dest_y)
+ {
+ you.running = 0;
+ return ;
+ }
+
+ // How many points are we currently considering? We start off with just one
+ // point, and spread outwards like a flood-filler.
+ int points = 1;
+
+ // How many points we'll consider next iteration.
+ int next_iter_points = 0;
+
+ // How far we've traveled from (start_x, start_y), in moves (a diagonal move
+ // is no longer than an orthogonal move).
+ int traveled_distance = 1;
+
+ // Which index of the circumference array are we currently looking at?
+ int circ_index = 0;
+
+ // The circumference points of the floodfilled area, for this iteration
+ // and the next (this iteration's points being circ_index amd the next one's
+ // being !circ_index).
+ static FixedVector<coord_def, GXM * GYM> circumference[2];
+
+ // Coordinates of all discovered traps. If we're exploring instead of
+ // travelling, we'll reseed from these points after we've explored the map
+ std::vector<coord_def> trap_seeds;
+
+ // When set to true, the travel code ignores features, traps and hostile
+ // terrain, and simply tries to map contiguous floorspace. Will only be set
+ // to true if we're exploring, instead of travelling.
+ bool ignore_hostile = false;
+
+ // Set the seed point
+ circumference[circ_index][0].x = start_x;
+ circumference[circ_index][0].y = start_y;
+
+ // Zap out previous distances array
+ memset(point_distance, 0, sizeof point_distance);
+
+ for ( ; points > 0; ++traveled_distance, circ_index = !circ_index,
+ points = next_iter_points, next_iter_points = 0)
+ {
+ for (int i = 0; i < points; ++i)
+ {
+ int x = circumference[circ_index][i].x,
+ y = circumference[circ_index][i].y;
+
+ // (x,y) is a known (explored) location - we never put unknown
+ // points in the circumference vector, so we don't need to examine
+ // the map array, just the grid array.
+ unsigned char feature = grd[x][y];
+
+ // If this is a feature that'll take time to travel past, we
+ // simulate that extra turn by taking this feature next turn,
+ // thereby artificially increasing traveled_distance.
+ //
+ // Note: I don't know how slow walking through shallow water and
+ // opening closed doors is - right now it's considered to have
+ // the cost of two normal moves.
+ int feat_cost = feature_traverse_cost(feature);
+ if (feat_cost > 1
+ && point_distance[x][y] > traveled_distance - feat_cost)
+ {
+ circumference[!circ_index][next_iter_points].x = x;
+ circumference[!circ_index][next_iter_points].y = y;
+ next_iter_points++;
+ continue;
+ }
+
+ // For each point, we look at all surrounding points. Take them
+ // orthogonals first so that the travel path doesn't zigzag all over
+ // the map. Note the (dir = 1) is intentional assignment.
+ for (int dir = 0; dir < 8; (dir += 2) == 8 && (dir = 1))
+ {
+ int dx = x + Compass[dir].x, dy = y + Compass[dir].y;
+
+ if (dx <= 0 || dx >= GXM || dy <= 0 || dy >= GYM) continue;
+
+ unsigned char envf = env.map[dx - 1][dy - 1];
+
+ if (floodout && you.running == RUN_EXPLORE
+ && !is_player_mapped(envf))
+ {
+ // Setting run_x and run_y here is evil - this function
+ // should ideally not modify game state in any way.
+ you.run_x = x;
+ you.run_y = y;
+
+ return;
+ }
+
+ if ((dx != dest_x || dy != dest_y)
+ && !is_travel_ok(dx, dy, ignore_hostile))
+ {
+ // This point is not okay to travel on, but if this is a
+ // trap, we'll want to put it on the feature vector anyway.
+ if (is_reseedable(dx, dy)
+ && !point_distance[dx][dy]
+ && (dx != start_x || dy != start_y))
+ {
+ if (features)
+ {
+ coord_def c = { dx, dy };
+ if (is_trap(dx, dy) || is_exclude_root(dx, dy))
+ features->push_back(c);
+ trap_seeds.push_back(c);
+ }
+
+ // Appropriate mystic number. Nobody else should check
+ // this number, since this square is unsafe for travel.
+ point_distance[dx][dy] =
+ is_exclude_root(dx, dy)? PD_EXCLUDED :
+ is_excluded(dx, dy) ? PD_EXCLUDED_RADIUS :
+ PD_TRAP;
+ }
+ continue;
+ }
+
+ if (dx == dest_x && dy == dest_y)
+ {
+ // Hallelujah, we're home!
+ if (is_safe(x, y) && move_x && move_y)
+ {
+ *move_x = sgn(x - dest_x);
+ *move_y = sgn(y - dest_y);
+ }
+ return ;
+ }
+ else if (!point_distance[dx][dy])
+ {
+ // This point is going to be on the agenda for the next
+ // iteration
+ circumference[!circ_index][next_iter_points].x = dx;
+ circumference[!circ_index][next_iter_points].y = dy;
+ next_iter_points++;
+
+ point_distance[dx][dy] = traveled_distance;
+
+ // Negative distances here so that show_map can colour
+ // the map differently for these squares.
+ if (ignore_hostile)
+ {
+ point_distance[dx][dy] = -point_distance[dx][dy];
+ if (is_exclude_root(dx, dy))
+ point_distance[dx][dy] = PD_EXCLUDED;
+ else if (is_excluded(dx, dy))
+ point_distance[dx][dy] = PD_EXCLUDED_RADIUS;
+ }
+
+ unsigned char feature = grd[dx][dy];
+ if (features && !ignore_hostile
+ && ((feature != DNGN_FLOOR
+ && feature != DNGN_SHALLOW_WATER
+ && feature != DNGN_DEEP_WATER
+ && feature != DNGN_LAVA)
+ || is_waypoint(dx, dy)
+#ifdef STASH_TRACKING
+ || is_stash(lev, dx, dy)
+#endif
+ )
+ && (dx != start_x || dy != start_y))
+ {
+ coord_def c = { dx, dy };
+ features->push_back(c);
+ }
+
+ if (features && is_exclude_root(dx, dy) && dx != start_x
+ && dy != start_y)
+ {
+ coord_def c = { dx, dy };
+ features->push_back(c);
+ }
+ }
+ } // for (dir = 0; dir < 8 ...
+ } // for (i = 0; i < points ...
+
+ if (!next_iter_points && features && !move_x && !ignore_hostile
+ && trap_seeds.size())
+ {
+ // Reseed here
+ for (unsigned i = 0; i < trap_seeds.size(); ++i)
+ circumference[!circ_index][i] = trap_seeds[i];
+ next_iter_points = trap_seeds.size();
+ ignore_hostile = true;
+ }
+ } // for ( ; points > 0 ...
+}
+
+// Mappings of which branches spring from which other branches, essential to
+// walk backwards up the tree. Yes, this is a horrible abuse of coord_def.
+static coord_def branch_backout[] =
+{
+ { BRANCH_SWAMP, BRANCH_LAIR },
+ { BRANCH_SLIME_PITS, BRANCH_LAIR },
+ { BRANCH_SNAKE_PIT, BRANCH_LAIR },
+
+ { BRANCH_HALL_OF_BLADES, BRANCH_VAULTS },
+ { BRANCH_CRYPT, BRANCH_VAULTS },
+
+ { BRANCH_TOMB, BRANCH_CRYPT },
+
+ { BRANCH_ELVEN_HALLS, BRANCH_ORCISH_MINES },
+
+ { BRANCH_ORCISH_MINES, BRANCH_MAIN_DUNGEON },
+ { BRANCH_HIVE, BRANCH_MAIN_DUNGEON },
+ { BRANCH_LAIR, BRANCH_MAIN_DUNGEON },
+ { BRANCH_VAULTS, BRANCH_MAIN_DUNGEON },
+ { BRANCH_HALL_OF_ZOT, BRANCH_MAIN_DUNGEON },
+ { BRANCH_ECUMENICAL_TEMPLE, BRANCH_MAIN_DUNGEON },
+};
+
+/*
+ * Given a branch id, returns the parent branch. If the branch id is not found,
+ * returns BRANCH_MAIN_DUNGEON.
+ */
+unsigned char find_parent_branch(unsigned char br)
+{
+ for (unsigned i = 0;
+ i < sizeof(branch_backout) / sizeof(branch_backout[0]);
+ i++)
+ {
+ if (branch_backout[i].x == br)
+ return (unsigned char) branch_backout[i].y;
+ }
+ return 0;
+}
+
+extern FixedVector<char, MAX_BRANCHES> stair_level;
+
+void find_parent_branch(unsigned char br, int depth,
+ unsigned char *pb, int *pd)
+{
+ int lev = stair_level[br];
+ if (lev <= 0)
+ {
+ *pb = 0;
+ *pd = 0; // Check depth before using *pb.
+ return ;
+ }
+
+ *pb = find_parent_branch(br);
+ *pd = subdungeon_depth(*pb, lev);
+}
+
+// Appends the passed in branch/depth to the given vector, then attempts to
+// repeat the operation with the parent branch of the given branch.
+//
+// As an example of what it does, assume this dungeon structure
+// Stairs to lair on D:11
+// Stairs to snake pit on lair:5
+//
+// If level 3 of the snake pit is the level we want to track back from,
+// we'd call trackback(vec, BRANCH_SNAKE_PIT, 3), and the resulting vector will
+// look like:
+// { BRANCH_SNAKE_PIT, 3 }, { BRANCH_LAIR, 5 }, { BRANCH_MAIN_DUNGEON, 11 }
+// (Assuming, of course, that the vector started out empty.)
+//
+void trackback(std::vector<level_id> &vec,
+ unsigned char branch, int subdepth)
+{
+ if (subdepth < 1 || subdepth > MAX_LEVELS) return;
+
+ level_id lid( branch, subdepth );
+ vec.push_back(lid);
+
+ if (branch != BRANCH_MAIN_DUNGEON)
+ {
+ unsigned char pb;
+ int pd;
+ find_parent_branch(branch, subdepth, &pb, &pd);
+ if (pd)
+ trackback(vec, pb, pd);
+ }
+}
+
+void track_intersect(std::vector<level_id> &cur,
+ std::vector<level_id> &targ,
+ level_id *cx)
+{
+ cx->branch = 0;
+ cx->depth = -1;
+
+ int us = cur.size() - 1, them = targ.size() - 1;
+
+ for ( ; us >= 0 && them >= 0; us--, them--)
+ {
+ if (cur[us].branch != targ[them].branch)
+ break;
+ }
+
+ us++, them++;
+
+ if (us < (int) cur.size() && them < (int) targ.size() && us >= 0 &&
+ them >= 0)
+ *cx = targ[them];
+}
+
+/*
+ * Returns the number of stairs the player would need to take to go from
+ * the 'first' level to the 'second' level. If there's no obvious route between
+ * 'first' and 'second', returns -1. If first == second, returns 0.
+ */
+int level_distance(level_id first, level_id second)
+{
+ if (first == second) return 0;
+
+ std::vector<level_id> fv, sv;
+
+ // If in the same branch, easy.
+ if (first.branch == second.branch)
+ return abs(first.depth - second.depth);
+
+ // Figure out the dungeon structure between the two levels.
+ trackback(fv, first.branch, first.depth);
+ trackback(sv, second.branch, second.depth);
+
+ level_id intersect;
+ track_intersect(fv, sv, &intersect);
+
+ if (intersect.depth == -1) // No common ground?
+ return -1;
+
+ int distance = 0;
+ // If the common branch is not the same as the current branch, we'll
+ // have to walk up the branch tree until we get to the common branch.
+ while (first.branch != intersect.branch)
+ {
+ distance += first.depth;
+
+ find_parent_branch(first.branch, first.depth,
+ &first.branch, &first.depth);
+ if (!first.depth)
+ return -1;
+ }
+
+ // Now first.branch == intersect.branch
+ distance += abs(first.depth - intersect.depth);
+
+ bool ignore_end = true;
+ for (int i = sv.size() - 1; i >= 0; --i)
+ {
+ if (ignore_end)
+ {
+ if (sv[i].branch == intersect.branch)
+ ignore_end = false;
+ continue;
+ }
+ distance += sv[i].depth;
+ }
+ return distance;
+}
+
+static struct
+{
+ const char *branch_name, *full_name;
+ char hotkey;
+} branches [] =
+{
+ { "Dungeon", "the Main Dungeon", 'D' },
+ { "Dis", "Dis", 'I' },
+ { "Gehenna", "Gehenna", 'W' },
+ { "Hell", "Hell", 'U' },
+ { "Cocytus", "Cocytus", 'X' },
+ { "Tartarus", "Tartarus", 'Y' },
+ { "Inferno", "", 'R' },
+ { "The Pit", "", '0' },
+ { "------------", "", '-' },
+ { "------------", "", '-' },
+ { "Orcish Mines", "the Orcish Mines", 'O' },
+ { "Hive", "the Hive", 'H' },
+ { "Lair", "the Lair", 'L' },
+ { "Slime Pits", "the Slime Pits", 'M' },
+ { "Vaults", "the Vaults", 'V' },
+ { "Crypt", "the Crypt", 'C' },
+ { "Hall of Blades", "the Hall of Blades", 'B' },
+ { "Zot", "the Realm of Zot", 'Z' },
+ { "Temple", "the Ecumenical Temple", 'T' },
+ { "Snake Pit", "the Snake Pit", 'P' },
+ { "Elven Halls", "the Elven Halls", 'E' },
+ { "Tomb", "the Tomb", 'G' },
+ { "Swamp", "the Swamp", 'S' }
+};
+
+static struct
+{
+ const char *abbr;
+ unsigned char branch;
+} branch_abbrvs[] =
+{
+ { "D", BRANCH_MAIN_DUNGEON },
+ { "Dis", BRANCH_DIS },
+ { "Geh", BRANCH_GEHENNA },
+ { "Hell", BRANCH_VESTIBULE_OF_HELL },
+ { "Coc", BRANCH_COCYTUS },
+ { "Tar", BRANCH_TARTARUS },
+ { "inf", BRANCH_INFERNO },
+ { "pit", BRANCH_THE_PIT },
+ { "Orc", BRANCH_ORCISH_MINES },
+ { "Hive", BRANCH_HIVE },
+ { "Lair", BRANCH_LAIR },
+ { "Slime", BRANCH_SLIME_PITS },
+ { "Vault", BRANCH_VAULTS },
+ { "Crypt", BRANCH_CRYPT },
+ { "Blades", BRANCH_HALL_OF_BLADES },
+ { "Zot", BRANCH_HALL_OF_ZOT },
+ { "Temple", BRANCH_ECUMENICAL_TEMPLE },
+ { "Snake", BRANCH_SNAKE_PIT },
+ { "Elf", BRANCH_ELVEN_HALLS },
+ { "Tomb", BRANCH_TOMB },
+ { "Swamp", BRANCH_SWAMP },
+};
+
+void set_trans_travel_dest(char *buffer, int maxlen, const level_pos &target)
+{
+ if (!buffer) return;
+
+ const char *branch = NULL;
+ for (unsigned i = 0; i < sizeof(branch_abbrvs) / sizeof(branch_abbrvs[0]);
+ ++i)
+ {
+ if (branch_abbrvs[i].branch == target.id.branch)
+ {
+ branch = branch_abbrvs[i].abbr;
+ break;
+ }
+ }
+
+ if (!branch) return;
+
+ unsigned char branch_id = target.id.branch;
+ // Show level+depth information and tack on an @(x,y) if the player
+ // wants to go to a specific square on the target level. We don't use
+ // actual coordinates since that will give away level information we
+ // don't want the player to have.
+ if (branch_id != BRANCH_ECUMENICAL_TEMPLE
+ && branch_id != BRANCH_HALL_OF_BLADES
+ && branch_id != BRANCH_VESTIBULE_OF_HELL)
+ snprintf(buffer, maxlen, "%s:%d%s", branch, target.id.depth,
+ target.pos.x != -1? " @ (x,y)" : "");
+ else
+ snprintf(buffer, maxlen, "%s%s", branch,
+ target.pos.x != -1? " @ (x,y)" : "");
+}
+
+// Returns the level on the given branch that's closest to the player's
+// current location.
+static int get_nearest_level_depth(unsigned char branch)
+{
+ int depth = 1;
+
+ // Hell needs special treatment, because we can't walk up
+ // Hell and its branches to the main dungeon.
+ if (branch == BRANCH_MAIN_DUNGEON &&
+ (player_in_branch( BRANCH_VESTIBULE_OF_HELL ) ||
+ player_in_branch( BRANCH_COCYTUS ) ||
+ player_in_branch( BRANCH_TARTARUS ) ||
+ player_in_branch( BRANCH_DIS ) ||
+ player_in_branch( BRANCH_GEHENNA )))
+ return you.hell_exit + 1;
+
+ level_id id = level_id::get_current_level_id();
+ do
+ {
+ find_parent_branch(id.branch, id.depth,
+ &id.branch, &id.depth);
+ if (id.depth && id.branch == branch)
+ {
+ depth = id.depth;
+ break;
+ }
+ } while (id.depth);
+
+ return depth;
+}
+
+static char trans_travel_dest[30];
+
+// Returns true if the player character knows of the existence of the given
+// branch (which would make the branch a valid target for interlevel travel).
+static bool is_known_branch(unsigned char branch)
+{
+ // The main dungeon is always known.
+ if (branch == BRANCH_MAIN_DUNGEON) return true;
+
+ // If we're in the branch, it darn well is known.
+ if (you.where_are_you == branch) return true;
+
+ // If the overmap knows the stairs to this branch, we know the branch.
+ unsigned char par;
+ int pdep;
+ find_parent_branch(branch, 1, &par, &pdep);
+ if (pdep)
+ return true;
+
+ // Do a brute force search in the travel cache for this branch.
+ return travel_cache.is_known_branch(branch);
+
+ // Doing this to check for the reachability of a branch is slow enough to
+ // be noticeable.
+
+ // Ask interlevel travel if it knows how to go there. If it knows how to
+ // get (partway) there, return true.
+
+ // level_pos pos;
+ // pos.id.branch = branch;
+ // pos.id.depth = 1;
+
+ // return find_transtravel_square(pos, false) != 0;
+}
+
+/*
+ * Returns a list of the branches that the player knows the location of the
+ * stairs to, in the same order as overmap.cc lists them.
+ */
+static std::vector<unsigned char> get_known_branches()
+{
+ // Lifted from overmap.cc. XXX: Move this to a better place?
+ const unsigned char list_order[] =
+ {
+ BRANCH_MAIN_DUNGEON,
+ BRANCH_ECUMENICAL_TEMPLE,
+ BRANCH_ORCISH_MINES, BRANCH_ELVEN_HALLS,
+ BRANCH_LAIR, BRANCH_SWAMP, BRANCH_SLIME_PITS, BRANCH_SNAKE_PIT,
+ BRANCH_HIVE,
+ BRANCH_VAULTS, BRANCH_HALL_OF_BLADES, BRANCH_CRYPT, BRANCH_TOMB,
+ BRANCH_VESTIBULE_OF_HELL,
+ BRANCH_DIS, BRANCH_GEHENNA, BRANCH_COCYTUS, BRANCH_TARTARUS,
+ BRANCH_HALL_OF_ZOT
+ };
+
+ std::vector<unsigned char> branches;
+ for (unsigned i = 0; i < sizeof list_order / sizeof(*list_order); ++i)
+ {
+ if (is_known_branch(list_order[i]))
+ branches.push_back(list_order[i]);
+ }
+
+ return branches;
+}
+
+static int prompt_travel_branch()
+{
+ unsigned char branch = BRANCH_MAIN_DUNGEON; // Default
+ std::vector<unsigned char> br = get_known_branches();
+
+ // Don't kill the prompt even if the only branch we know is the main dungeon
+ // This keeps things consistent for the player.
+ if (br.size() < 1) return branch;
+
+ bool waypoint_list = false;
+ int waycount = travel_cache.get_waypoint_count();
+ for ( ; ; )
+ {
+ char buf[100];
+ if (waypoint_list)
+ travel_cache.list_waypoints();
+ else
+ {
+ int linec = 0;
+ std::string line;
+ for (int i = 0, count = br.size(); i < count; ++i, ++linec)
+ {
+ if (linec == 4)
+ {
+ linec = 0;
+ mpr(line.c_str());
+ line = "";
+ }
+ snprintf(buf, sizeof buf, "(%c) %-14s ", branches[br[i]].hotkey,
+ branches[br[i]].branch_name);
+ line += buf;
+ }
+ if (line.length())
+ mpr(line.c_str());
+ }
+
+ char shortcuts[100];
+ *shortcuts = 0;
+ if (*trans_travel_dest || waycount || waypoint_list)
+ {
+ strncpy(shortcuts, "(", sizeof shortcuts);
+ if (waypoint_list)
+ strncat(shortcuts, "[*] lists branches", sizeof shortcuts);
+ else if (waycount)
+ strncat(shortcuts, "[*] lists waypoints", sizeof shortcuts);
+
+ if (*trans_travel_dest)
+ {
+ char travel_dest[60];
+ snprintf(travel_dest, sizeof travel_dest, "[Enter] for %s",
+ trans_travel_dest);
+ if (waypoint_list || waycount)
+ strncat( shortcuts, ", ", sizeof shortcuts);
+ strncat(shortcuts, travel_dest, sizeof shortcuts);
+ }
+ strncat(shortcuts, ") ", sizeof shortcuts);
+ }
+ snprintf(buf, sizeof buf, "Where do you want to go? %s", shortcuts);
+ mpr(buf, MSGCH_PROMPT);
+
+ int keyin = get_ch();
+ switch (keyin)
+ {
+ case ESCAPE:
+ return (ID_CANCEL);
+ case '\n': case '\r':
+ return (ID_REPEAT);
+ case '<':
+ return (ID_UP);
+ case '>':
+ return (ID_DOWN);
+ case '*':
+ if (waypoint_list || waycount)
+ {
+ waypoint_list = !waypoint_list;
+ mesclr();
+ continue;
+ }
+ break;
+ default:
+ // Is this a branch hotkey?
+ for (int i = 0, count = br.size(); i < count; ++i)
+ {
+ if (toupper(keyin) == branches[br[i]].hotkey)
+ return (br[i]);
+ }
+
+ // Possibly a waypoint number?
+ if (keyin >= '0' && keyin <= '9')
+ return (-1 - (keyin - '0'));
+ return (ID_CANCEL);
+ }
+ }
+}
+
+static int prompt_travel_depth(unsigned char branch)
+{
+ // Handle one-level branches by not prompting.
+ if (branch == BRANCH_ECUMENICAL_TEMPLE ||
+ branch == BRANCH_VESTIBULE_OF_HELL ||
+ branch == BRANCH_HALL_OF_BLADES)
+ return 1;
+
+ char buf[100];
+ int depth = get_nearest_level_depth(branch);
+
+ snprintf(buf, sizeof buf, "What level of %s do you want to go to? "
+ "[default %d] ", branches[branch].full_name, depth);
+ mesclr();
+ mpr(buf, MSGCH_PROMPT);
+
+ if (!cancelable_get_line( buf, sizeof buf ))
+ return 0;
+
+ if (*buf)
+ depth = atoi(buf);
+
+ return depth;
+}
+
+static bool is_hell_branch(int branch)
+{
+ return branch == BRANCH_DIS || branch == BRANCH_TARTARUS
+ || branch == BRANCH_COCYTUS || branch == BRANCH_GEHENNA;
+
+}
+
+static level_pos find_up_level()
+{
+ level_id curr = level_id::get_current_level_id();
+ curr.depth--;
+
+ if (is_hell_branch(curr.branch))
+ {
+ curr.branch = BRANCH_VESTIBULE_OF_HELL;
+ curr.depth = 1;
+ return (curr);
+ }
+
+ if (curr.depth < 1)
+ {
+ if (curr.branch != BRANCH_MAIN_DUNGEON)
+ {
+ level_id parent;
+ find_parent_branch(curr.branch, curr.depth,
+ &parent.branch, &parent.depth);
+ if (parent.depth > 0)
+ return (parent);
+ else if (curr.branch == BRANCH_VESTIBULE_OF_HELL)
+ {
+ parent.branch = BRANCH_MAIN_DUNGEON;
+ parent.depth = you.hell_exit + 1;
+ return (parent);
+ }
+ }
+ return level_pos();
+ }
+
+ return (curr);
+}
+
+static level_pos find_down_level()
+{
+ level_id curr = level_id::get_current_level_id();
+ curr.depth++;
+ return (curr);
+}
+
+static level_pos prompt_translevel_target()
+{
+ level_pos target;
+ int branch = prompt_travel_branch();
+
+ if (branch == ID_CANCEL)
+ return (target);
+
+ // If user chose to repeat last travel, return that.
+ if (branch == ID_REPEAT)
+ return (travel_target);
+
+ if (branch == ID_UP)
+ {
+ target = find_up_level();
+ if (target.id.depth > -1)
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ target);
+ return (target);
+ }
+
+ if (branch == ID_DOWN)
+ {
+ target = find_down_level();
+ if (target.id.depth > -1)
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ target);
+ return (target);
+ }
+
+ if (branch < 0)
+ {
+ travel_cache.travel_to_waypoint(-branch - 1);
+ return target;
+ }
+
+ target.id.branch = branch;
+
+ // User's chosen a branch, so now we ask for a level.
+ target.id.depth = prompt_travel_depth(target.id.branch);
+
+ if (target.id.depth < 1 || target.id.depth >= MAX_LEVELS)
+ target.id.depth = -1;
+
+ if (target.id.depth > -1)
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ target);
+
+ return target;
+}
+
+void start_translevel_travel(const level_pos &pos)
+{
+ travel_target = pos;
+
+ if (pos.id != level_id::get_current_level_id())
+ {
+ if (!loadlev_populate_stair_distances(pos))
+ {
+ mpr("Level memory is imperfect, aborting.");
+ return ;
+ }
+ }
+ else
+ populate_stair_distances(pos);
+
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ travel_target);
+ start_translevel_travel(false);
+}
+
+void start_translevel_travel(bool prompt_for_destination)
+{
+ // Update information for this level. We need it even for the prompts, so
+ // we can't wait to confirm that the user chose to initiate travel.
+ travel_cache.get_level_info(level_id::get_current_level_id()).update();
+
+ if (prompt_for_destination)
+ {
+ // prompt_translevel_target may actually initiate travel directly if
+ // the user chose a waypoint instead of a branch + depth. As far as
+ // we're concerned, if the target depth is unset, we need to take no
+ // further action.
+ level_pos target = prompt_translevel_target();
+ if (target.id.depth == -1) return;
+
+ travel_target = target;
+ }
+
+ if (level_id::get_current_level_id() == travel_target.id &&
+ (travel_target.pos.x == -1 ||
+ (travel_target.pos.x == you.x_pos &&
+ travel_target.pos.y == you.y_pos)))
+ {
+ mpr("You're already here!");
+ return ;
+ }
+
+ if (travel_target.id.depth > -1)
+ {
+ you.running = RUN_INTERLEVEL;
+ you.run_x = you.run_y = 0;
+ last_stair.depth = -1;
+ start_running();
+ }
+}
+
+int stair_direction(int stair)
+{
+ return ((stair < DNGN_STONE_STAIRS_UP_I
+ || stair > DNGN_ROCK_STAIRS_UP)
+ && (stair < DNGN_RETURN_FROM_ORCISH_MINES
+ || stair > DNGN_RETURN_FROM_SWAMP))
+ ? '>' : '<';
+}
+
+int trans_negotiate_stairs()
+{
+ return stair_direction(grd[you.x_pos][you.y_pos]);
+}
+
+int absdungeon_depth(unsigned char branch, int subdepth)
+{
+ int realdepth = subdepth - 1;
+
+ if (branch >= BRANCH_ORCISH_MINES && branch <= BRANCH_SWAMP)
+ realdepth = subdepth + you.branch_stairs[branch - 10];
+
+ if (branch >= BRANCH_DIS && branch <= BRANCH_THE_PIT)
+ realdepth = subdepth + 26;
+
+ return realdepth;
+}
+
+int subdungeon_depth(unsigned char branch, int depth)
+{
+ int curr_subdungeon_level = depth + 1;
+
+ // maybe last part better expresssed as <= PIT {dlb}
+ if (branch >= BRANCH_DIS && branch <= BRANCH_THE_PIT)
+ curr_subdungeon_level = depth - 26;
+
+ /* Remember, must add this to the death_string in ouch */
+ if (branch >= BRANCH_ORCISH_MINES && branch <= BRANCH_SWAMP)
+ curr_subdungeon_level = depth
+ - you.branch_stairs[branch - 10];
+
+ return curr_subdungeon_level;
+}
+
+static int target_distance_from(const coord_def &pos)
+{
+ for (int i = 0, count = curr_stairs.size(); i < count; ++i)
+ if (curr_stairs[i].position == pos)
+ return curr_stairs[i].distance;
+ return -1;
+}
+
+/*
+ * Sets best_stair to the coordinates of the best stair on the player's current
+ * level to take to get to the 'target' level. Should be called with 'distance'
+ * set to 0, 'stair' set to (you.x_pos, you.y_pos) and 'best_distance' set to
+ * -1. 'cur' should be the player's current level.
+ *
+ * If best_stair remains unchanged when this function returns, there is no
+ * travel-safe path between the player's current level and the target level OR
+ * the player's current level *is* the target level.
+ *
+ * This function relies on the point_distance array being correctly populated
+ * with a floodout call to find_travel_pos starting from the player's location.
+ */
+static int find_transtravel_stair( const level_id &cur,
+ const level_pos &target,
+ int distance,
+ const coord_def &stair,
+ level_id &closest_level,
+ int &best_level_distance,
+ coord_def &best_stair)
+{
+ int local_distance = -1;
+ level_id player_level = level_id::get_current_level_id();
+
+ // Have we reached the target level?
+ if (cur == target.id)
+ {
+ // If there's no target position on the target level, or we're on the
+ // target, we're home.
+ if (target.pos.x == -1 || target.pos == stair)
+ return distance;
+
+ // If there *is* a target position, we need to work out our distance
+ // from it.
+ int deltadist = target_distance_from(stair);
+
+ if (deltadist == -1 && cur == player_level)
+ {
+ // Okay, we don't seem to have a distance available to us, which
+ // means we're either (a) not standing on stairs or (b) whoever
+ // initiated interlevel travel didn't call
+ // populate_stair_distances. Assuming we're not on stairs, that
+ // situation can arise only if interlevel travel has been triggered
+ // for a location on the same level. If that's the case, we can get
+ // the distance off the point_distance matrix.
+ deltadist = point_distance[target.pos.x][target.pos.y];
+ if (!deltadist &&
+ (stair.x != target.pos.x || stair.y != target.pos.y))
+ deltadist = -1;
+ }
+
+ if (deltadist != -1)
+ {
+ local_distance = distance + deltadist;
+
+ // See if this is a degenerate case of interlevel travel:
+ // A degenerate case of interlevel travel decays to normal travel;
+ // we identify this by checking if:
+ // a. The current level is the target level.
+ // b. The target square is reachable from the 'current' square.
+ // c. The current square is where the player is.
+ //
+ // Note that even if this *is* degenerate, interlevel travel may
+ // still be able to find a shorter route, since it can consider
+ // routes that leave and reenter the current level.
+ if (player_level == target.id && stair.x == you.x_pos
+ && stair.y == you.y_pos)
+ best_stair = target.pos;
+
+ // The local_distance is already set, but there may actually be
+ // stairs we can take that'll get us to the target faster than the
+ // direct route, so we also try the stairs.
+ }
+ }
+
+ LevelInfo &li = travel_cache.get_level_info(cur);
+ std::vector<stair_info> &stairs = li.get_stairs();
+
+ // this_stair being NULL is perfectly acceptable, since we start with
+ // coords as the player coords, and the player need not be standing on
+ // stairs.
+ stair_info *this_stair = li.get_stair(stair);
+
+ if (!this_stair && cur != player_level)
+ {
+ // Whoops, there's no stair in the travel cache for the current
+ // position, and we're not on the player's current level (i.e., there
+ // certainly *should* be a stair here). Since we can't proceed in any
+ // reasonable way, bail out.
+ return local_distance;
+ }
+
+ 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)
+ {
+ deltadist = point_distance[si.position.x][si.position.y];
+ if (!deltadist &&
+ (you.x_pos != si.position.x || you.y_pos != si.position.y))
+ deltadist = -1;
+ }
+
+ // 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;
+
+ int dist2stair = distance + deltadist;
+ if (si.distance == -1 || si.distance > dist2stair)
+ {
+ si.distance = dist2stair;
+
+ dist2stair += Options.travel_stair_cost;
+ ++dist2stair; // Account for the cost of taking the stairs
+
+ // Already too expensive? Short-circuit.
+ if (local_distance != -1 && dist2stair >= local_distance)
+ continue;
+
+ const level_pos &dest = si.destination;
+
+ // We can only short-circuit the stair-following process if we
+ // have no exact target location. If there *is* an exact target
+ // location, we can't follow stairs for which we have incomplete
+ // information.
+ if (target.pos.x == -1 && dest.id == target.id)
+ {
+ if (local_distance == -1 || local_distance > dist2stair)
+ {
+ local_distance = dist2stair;
+ if (cur == player_level && you.x_pos == stair.x &&
+ you.y_pos == stair.y)
+ best_stair = si.position;
+ }
+ continue;
+ }
+
+ if (dest.id.depth > -1) { // We have a valid level descriptor.
+ int dist = level_distance(dest.id, target.id);
+ if (dist != -1 &&
+ (dist < best_level_distance ||
+ best_level_distance == -1))
+ {
+ best_level_distance = dist;
+ closest_level = dest.id;
+ }
+ }
+
+ // If we don't know where these stairs go, we can't take them.
+ if (!dest.is_valid()) continue;
+
+ // We need to get the stairs at the new location and set the
+ // distance on them as well.
+ LevelInfo &lo = travel_cache.get_level_info(dest.id);
+ stair_info *so = lo.get_stair(dest.pos);
+
+ if (so)
+ {
+ if (so->distance == -1 || so->distance > dist2stair)
+ so->distance = dist2stair;
+ else
+ continue; // We've already been here.
+ }
+
+ // Okay, take these stairs and keep going.
+ int newdist = find_transtravel_stair(dest.id, target,
+ dist2stair, dest.pos, closest_level,
+ best_level_distance, best_stair);
+ if (newdist != -1 &&
+ (local_distance == -1 || local_distance > newdist))
+ {
+ local_distance = newdist;
+ if (cur == player_level && you.x_pos == stair.x &&
+ you.y_pos == stair.y)
+ best_stair = si.position;
+ }
+ }
+ }
+ return local_distance;
+}
+
+static bool loadlev_populate_stair_distances(const level_pos &target)
+{
+ crawl_environment tmp = env;
+ if (!travel_load_map(target.id.branch,
+ absdungeon_depth(target.id.branch, target.id.depth)))
+ {
+ env = tmp;
+ return false;
+ }
+
+ std::vector<coord_def> old_excludes = curr_excludes;
+
+ curr_excludes.clear();
+ LevelInfo &li = travel_cache.get_level_info(target.id);
+ li.set_level_excludes();
+
+ populate_stair_distances(target);
+
+ env = tmp;
+ curr_excludes = old_excludes;
+ return !curr_stairs.empty();
+}
+
+static void populate_stair_distances(const level_pos &target)
+{
+ // Populate point_distance.
+ find_travel_pos(target.pos.x, target.pos.y, NULL, NULL, NULL);
+
+ LevelInfo &li = travel_cache.get_level_info(target.id);
+ const std::vector<stair_info> &stairs = li.get_stairs();
+
+ curr_stairs.clear();
+ for (int i = 0, count = stairs.size(); i < count; ++i)
+ {
+ stair_info si = stairs[i];
+ si.distance = point_distance[si.position.x][si.position.y];
+ if (!si.distance && target.pos != si.position)
+ si.distance = -1;
+ if (si.distance < -1)
+ si.distance = -1;
+
+ curr_stairs.push_back(si);
+ }
+}
+
+static int find_transtravel_square(const level_pos &target, bool verbose)
+{
+ level_id current = level_id::get_current_level_id();
+
+ coord_def best_stair = { -1, -1 };
+ coord_def cur_stair = { you.x_pos, you.y_pos };
+ level_id closest_level;
+ int best_level_distance = -1;
+ travel_cache.reset_distances();
+
+ find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, NULL);
+
+ find_transtravel_stair(current, target,
+ 0, cur_stair, closest_level,
+ best_level_distance, best_stair);
+
+ if (best_stair.x != -1 && best_stair.y != -1)
+ {
+ you.run_x = best_stair.x;
+ you.run_y = best_stair.y;
+ return 1;
+ }
+ else if (best_level_distance != -1 && closest_level != current
+ && target.pos.x == -1)
+ {
+ int current_dist = level_distance(current, target.id);
+ level_pos newlev;
+ newlev.id = closest_level;
+ if (current_dist == -1 || best_level_distance < current_dist)
+ return find_transtravel_square(newlev, verbose);
+ }
+
+ if (verbose && target.id != current)
+ mpr("Sorry, I don't know how to get there.");
+ return 0;
+}
+
+void start_travel(int x, int y)
+{
+ // Redundant target?
+ if (x == you.x_pos && y == you.y_pos) return ;
+
+ // Start running
+ you.running = RUN_TRAVEL;
+ you.run_x = x;
+ you.run_y = y;
+
+ // Remember where we're going so we can easily go back if interrupted.
+ you.travel_x = x;
+ you.travel_y = y;
+
+ // Check whether we can get to the square.
+ find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, NULL);
+
+ if (point_distance[x][y] == 0 &&
+ (x != you.x_pos || you.run_y != you.y_pos) &&
+ is_travel_ok(x, y, false))
+ {
+ // We'll need interlevel travel to get here.
+ travel_target.id = level_id::get_current_level_id();
+ travel_target.pos.x = x;
+ travel_target.pos.y = y;
+
+ you.running = RUN_INTERLEVEL;
+ you.run_x = you.run_y = 0;
+ last_stair.depth = -1;
+
+ // We need the distance of the target from the various stairs around.
+ populate_stair_distances(travel_target);
+
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ travel_target);
+ }
+
+ start_running();
+}
+
+void start_explore()
+{
+ you.running = RUN_EXPLORE;
+ if (Options.explore_stop)
+ {
+ // Clone shadow array off map
+ copy(env.map, mapshadow);
+ }
+ start_running();
+}
+
+/*
+ * Given a feature vector, arranges the features in the order that the player
+ * is most likely to be interested in. Currently, the only thing it does is to
+ * put altars of the player's religion at the front of the list.
+ */
+void arrange_features(std::vector<coord_def> &features)
+{
+ for (int i = 0, count = features.size(); i < count; ++i)
+ {
+ if (is_player_altar(features[i]))
+ {
+ int place = i;
+ // Shuffle this altar as far up the list as possible.
+ for (int j = place - 1; j >= 0; --j)
+ {
+ if (is_altar(features[j]))
+ {
+ if (is_player_altar(features[j]))
+ break;
+
+ coord_def temp = features[j];
+ features[j] = features[place];
+ features[place] = temp;
+
+ place = j;
+ }
+ }
+ }
+ }
+}
+
+//////////////////////////////////////////////////////////////////////////
+// Interlevel travel classes
+
+static void writeCoord(FILE *file, const coord_def &pos)
+{
+ writeShort(file, pos.x);
+ writeShort(file, pos.y);
+}
+
+static void readCoord(FILE *file, coord_def &pos)
+{
+ pos.x = readShort(file);
+ pos.y = readShort(file);
+}
+
+level_id level_id::get_current_level_id()
+{
+ level_id id;
+ id.branch = you.where_are_you;
+ id.depth = subdungeon_depth(you.where_are_you, you.your_level);
+
+ return id;
+}
+
+level_id level_id::get_next_level_id(const coord_def &pos)
+{
+ short gridc = grd[pos.x][pos.y];
+ level_id id = get_current_level_id();
+
+ switch (gridc)
+ {
+ case DNGN_STONE_STAIRS_DOWN_I: case DNGN_STONE_STAIRS_DOWN_II:
+ case DNGN_STONE_STAIRS_DOWN_III: case DNGN_ROCK_STAIRS_DOWN:
+ id.depth++;
+ break;
+ case DNGN_STONE_STAIRS_UP_I: case DNGN_STONE_STAIRS_UP_II:
+ case DNGN_STONE_STAIRS_UP_III: case DNGN_ROCK_STAIRS_UP:
+ id.depth--;
+ break;
+ case DNGN_ENTER_HELL:
+ id.branch = BRANCH_VESTIBULE_OF_HELL;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_DIS:
+ id.branch = BRANCH_DIS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_GEHENNA:
+ id.branch = BRANCH_GEHENNA;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_COCYTUS:
+ id.branch = BRANCH_COCYTUS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_TARTARUS:
+ id.branch = BRANCH_TARTARUS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_ORCISH_MINES:
+ id.branch = BRANCH_ORCISH_MINES;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_HIVE:
+ id.branch = BRANCH_HIVE;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_LAIR:
+ id.branch = BRANCH_LAIR;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_SLIME_PITS:
+ id.branch = BRANCH_SLIME_PITS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_VAULTS:
+ id.branch = BRANCH_VAULTS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_CRYPT:
+ id.branch = BRANCH_CRYPT;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_HALL_OF_BLADES:
+ id.branch = BRANCH_HALL_OF_BLADES;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_ZOT:
+ id.branch = BRANCH_HALL_OF_ZOT;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_TEMPLE:
+ id.branch = BRANCH_ECUMENICAL_TEMPLE;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_SNAKE_PIT:
+ id.branch = BRANCH_SNAKE_PIT;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_ELVEN_HALLS:
+ id.branch = BRANCH_ELVEN_HALLS;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_TOMB:
+ id.branch = BRANCH_TOMB;
+ id.depth = 1;
+ break;
+ case DNGN_ENTER_SWAMP:
+ id.branch = BRANCH_SWAMP;
+ id.depth = 1;
+ break;
+ case DNGN_RETURN_FROM_ORCISH_MINES: case DNGN_RETURN_FROM_HIVE:
+ case DNGN_RETURN_FROM_LAIR: case DNGN_RETURN_FROM_SLIME_PITS:
+ case DNGN_RETURN_FROM_VAULTS: case DNGN_RETURN_FROM_CRYPT:
+ case DNGN_RETURN_FROM_HALL_OF_BLADES: case DNGN_RETURN_FROM_ZOT:
+ case DNGN_RETURN_FROM_TEMPLE: case DNGN_RETURN_FROM_SNAKE_PIT:
+ case DNGN_RETURN_FROM_ELVEN_HALLS: case DNGN_RETURN_FROM_TOMB:
+ case DNGN_RETURN_FROM_SWAMP:
+ find_parent_branch(id.branch, id.depth, &id.branch, &id.depth);
+ if (!id.depth)
+ {
+ id.branch = find_parent_branch(you.where_are_you);
+ id.depth = -1;
+ }
+ break;
+ }
+ return id;
+}
+
+void level_id::save(FILE *file) const
+{
+ writeByte(file, branch);
+ writeShort(file, depth);
+}
+
+void level_id::load(FILE *file)
+{
+ branch = readByte(file);
+ depth = readShort(file);
+}
+
+void level_pos::save(FILE *file) const
+{
+ id.save(file);
+ writeCoord(file, pos);
+}
+
+void level_pos::load(FILE *file)
+{
+ id.load(file);
+ readCoord(file, pos);
+}
+
+void stair_info::save(FILE *file) const
+{
+ writeCoord(file, position);
+ destination.save(file);
+ writeByte(file, guessed_pos? 1 : 0);
+}
+
+void stair_info::load(FILE *file)
+{
+ readCoord(file, position);
+ destination.load(file);
+ guessed_pos = readByte(file) != 0;
+}
+
+LevelInfo::LevelInfo(const LevelInfo &other)
+{
+ stairs = other.stairs;
+ excludes = other.excludes;
+ int sz = stairs.size() * stairs.size();
+ stair_distances = new short [ sz ];
+ if (other.stair_distances)
+ memcpy(stair_distances, other.stair_distances, sz * sizeof(int));
+}
+
+const LevelInfo &LevelInfo::operator = (const LevelInfo &other)
+{
+ if (&other == this)
+ return *this;
+
+ stairs = other.stairs;
+ excludes = other.excludes;
+ int sz = stairs.size() * stairs.size();
+ delete [] stair_distances;
+ stair_distances = new short [ sz ];
+ if (other.stair_distances)
+ memcpy(stair_distances, other.stair_distances, sz * sizeof(short));
+ return *this;
+}
+
+LevelInfo::~LevelInfo()
+{
+ delete [] stair_distances;
+}
+
+void LevelInfo::set_level_excludes()
+{
+ curr_excludes = excludes;
+}
+
+void LevelInfo::update()
+{
+ // We need to update all stair information and distances on this level.
+ update_excludes();
+
+ // First, set excludes, so that stair distances will be correctly populated.
+ excludes = curr_excludes;
+
+ // First, we get all known stairs
+ std::vector<coord_def> stair_positions;
+ get_stairs(stair_positions);
+
+ // Make sure our stair list is correct.
+ correct_stair_list(stair_positions);
+
+ update_stair_distances();
+}
+
+void LevelInfo::update_stair_distances()
+{
+ // Now we update distances for all the stairs, relative to all other
+ // stairs.
+ for (int s = 0, end = stairs.size(); s < end; ++s)
+ {
+ // For each stair, we need to ask travel to populate the distance
+ // array.
+ find_travel_pos(stairs[s].position.x, stairs[s].position.y,
+ NULL, NULL, NULL);
+
+ for (int other = 0; other < end; ++other)
+ {
+ int ox = stairs[other].position.x,
+ oy = stairs[other].position.y;
+ int dist = point_distance[ox][oy];
+
+ // Note dist == 0 is illegal because we can't have two stairs on
+ // the same square.
+ if (dist <= 0) dist = -1;
+ stair_distances[ s * stairs.size() + other ] = dist;
+ stair_distances[ other * stairs.size() + s ] = dist;
+ }
+ }
+}
+
+void LevelInfo::update_stair(int x, int y, const level_pos &p, bool guess)
+{
+ stair_info *si = get_stair(x, y);
+
+ // What 'guess' signifies: whenever you take a stair from A to B, the
+ // travel code knows that the stair takes you from A->B. In that case,
+ // update_stair() is called with guess == false.
+ //
+ // Unfortunately, Crawl doesn't guarantee that A->B implies B->A, but the
+ // travel code has to assume that anyway (because that's what the player
+ // will expect), and call update_stair() again with guess == true.
+ //
+ // The idea of using 'guess' is that we'll update the stair's destination
+ // with a guess only if we know that the currently set destination is
+ // itself a guess.
+ //
+ if (si && (si->guessed_pos || !guess))
+ {
+ si->destination = p;
+ si->guessed_pos = guess;
+
+ if (!guess && p.id.branch == BRANCH_VESTIBULE_OF_HELL
+ && id.branch == BRANCH_MAIN_DUNGEON)
+ travel_hell_entry = p;
+ }
+}
+
+stair_info *LevelInfo::get_stair(int x, int y)
+{
+ coord_def c = { x, y };
+ return get_stair(c);
+}
+
+stair_info *LevelInfo::get_stair(const coord_def &pos)
+{
+ int index = get_stair_index(pos);
+ return index != -1? &stairs[index] : NULL;
+}
+
+int LevelInfo::get_stair_index(const coord_def &pos) const
+{
+ for (int i = stairs.size() - 1; i >= 0; --i)
+ {
+ if (stairs[i].position == pos)
+ return i;
+ }
+ return -1;
+}
+
+void LevelInfo::add_waypoint(const coord_def &pos)
+{
+ if (pos.x < 0 || pos.y < 0) return;
+
+ // First, make sure we don't already have this position in our stair list.
+ for (int i = 0, sz = stairs.size(); i < sz; ++i)
+ if (stairs[i].position == pos)
+ return;
+
+ stair_info si;
+ si.position = pos;
+ si.destination.id.depth = -2; // Magic number for waypoints.
+
+ stairs.push_back(si);
+
+ delete [] stair_distances;
+ stair_distances = new short [ stairs.size() * stairs.size() ];
+
+ update_stair_distances();
+}
+
+void LevelInfo::remove_waypoint(const coord_def &pos)
+{
+ for (std::vector<stair_info>::iterator i = stairs.begin();
+ i != stairs.end(); ++i)
+ {
+ if (i->position == pos && i->destination.id.depth == -2)
+ {
+ stairs.erase(i);
+ break;
+ }
+ }
+
+ delete [] stair_distances;
+ stair_distances = new short [ stairs.size() * stairs.size() ];
+
+ update_stair_distances();
+}
+
+void LevelInfo::correct_stair_list(const std::vector<coord_def> &s)
+{
+ // If we have a waypoint on this level, we'll always delete stair_distances
+ delete [] stair_distances;
+ stair_distances = NULL;
+
+ // 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)
+ {
+ // Waypoints are not stairs, so we skip them.
+ if (i->destination.id.depth == -2) continue;
+
+ bool found = false;
+ for (int j = s.size() - 1; j >= 0; --j)
+ {
+ if (s[j] == i->position)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ stairs.erase(i--);
+ }
+
+ // For each stair in 's', make sure we have a corresponding stair
+ // in 'stairs'.
+ for (int i = 0, sz = s.size(); i < sz; ++i)
+ {
+ bool found = false;
+ for (int j = stairs.size() - 1; j >= 0; --j)
+ {
+ if (s[i] == stairs[j].position)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ stair_info si;
+ si.position = s[i];
+ si.destination.id = level_id::get_next_level_id(s[i]);
+ if (si.destination.id.branch == BRANCH_VESTIBULE_OF_HELL
+ && id.branch == BRANCH_MAIN_DUNGEON
+ && travel_hell_entry.is_valid())
+ si.destination = travel_hell_entry;
+
+ // We don't know where on the next level these stairs go to, but
+ // that can't be helped. That information will have to be filled
+ // in whenever the player takes these stairs.
+ stairs.push_back(si);
+ }
+ }
+
+ stair_distances = new short [ stairs.size() * stairs.size() ];
+}
+
+int LevelInfo::distance_between(const stair_info *s1, const stair_info *s2)
+ const
+{
+ if (!s1 || !s2) return 0;
+ if (s1 == s2) return 0;
+
+ 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 ];
+}
+
+void LevelInfo::get_stairs(std::vector<coord_def> &st)
+{
+ // These are env map coords, not grid coordinates.
+ for (int y = 0; y < GYM - 1; ++y)
+ {
+ for (int x = 0; x < GXM - 1; ++x)
+ {
+ unsigned char grid = grd[x + 1][y + 1];
+ unsigned char envc = (unsigned char) env.map[x][y];
+
+ if (envc && is_travelable_stair(grid))
+ {
+ // Convert to grid coords, because that's what we use
+ // everywhere else.
+ coord_def stair = { x + 1, y + 1 };
+ st.push_back(stair);
+ }
+ }
+ }
+}
+
+void LevelInfo::reset_distances()
+{
+ for (int i = 0, count = stairs.size(); i < count; ++i)
+ {
+ stairs[i].reset_distance();
+ }
+}
+
+bool LevelInfo::is_known_branch(unsigned char branch) const
+{
+ for (int i = 0, count = stairs.size(); i < count; ++i)
+ {
+ if (stairs[i].destination.id.branch == branch)
+ return true;
+ }
+ return false;
+}
+
+void LevelInfo::travel_to_waypoint(const coord_def &pos)
+{
+ stair_info *target = get_stair(pos);
+ if (!target) return;
+
+ curr_stairs.clear();
+ for (int i = 0, sz = stairs.size(); i < sz; ++i)
+ {
+ if (stairs[i].destination.id.depth == -2) continue;
+
+ stair_info si = stairs[i];
+ si.distance = distance_between(target, &stairs[i]);
+
+ curr_stairs.push_back(si);
+ }
+
+ start_translevel_travel(false);
+}
+
+void LevelInfo::save(FILE *file) const
+{
+ int stair_count = stairs.size();
+ // How many stairs do we know of?
+ writeShort(file, stair_count);
+ for (int i = 0; i < stair_count; ++i)
+ stairs[i].save(file);
+
+ if (stair_count)
+ {
+ // XXX Assert stair_distances != NULL?
+ // Save stair distances as short ints.
+ for (int i = stair_count * stair_count - 1; i >= 0; --i)
+ writeShort(file, stair_distances[i]);
+ }
+
+ writeShort(file, excludes.size());
+ if (excludes.size())
+ {
+ for (int i = 0, count = excludes.size(); i < count; ++i)
+ {
+ writeShort(file, excludes[i].x);
+ writeShort(file, excludes[i].y);
+ }
+ }
+}
+
+#define EXCLUDE_LOAD_LIMIT 20
+void LevelInfo::load(FILE *file)
+{
+ stairs.clear();
+ int stair_count = readShort(file);
+ for (int i = 0; i < stair_count; ++i)
+ {
+ stair_info si;
+ si.load(file);
+ stairs.push_back(si);
+
+ if (id.branch == BRANCH_MAIN_DUNGEON &&
+ si.destination.id.branch == BRANCH_VESTIBULE_OF_HELL &&
+ !travel_hell_entry.is_valid() &&
+ si.destination.is_valid())
+ travel_hell_entry = si.destination;
+ }
+
+ if (stair_count)
+ {
+ delete [] stair_distances;
+ stair_distances = new short [ stair_count * stair_count ];
+ for (int i = stair_count * stair_count - 1; i >= 0; --i)
+ stair_distances[i] = readShort(file);
+ }
+
+ excludes.clear();
+ int nexcludes = readShort(file);
+ if (nexcludes)
+ {
+ for (int i = 0; i < nexcludes; ++i)
+ {
+ coord_def c;
+ c.x = readShort(file);
+ c.y = readShort(file);
+ excludes.push_back(c);
+ }
+ }
+}
+
+void LevelInfo::fixup()
+{
+ // The only fixup we do now is for the hell entry.
+ if (id.branch != BRANCH_MAIN_DUNGEON || !travel_hell_entry.is_valid())
+ return;
+ for (int i = 0, count = stairs.size(); i < count; ++i)
+ {
+ stair_info &si = stairs[i];
+ if (si.destination.id.branch == BRANCH_VESTIBULE_OF_HELL
+ && !si.destination.is_valid())
+ si.destination = travel_hell_entry;
+ }
+}
+
+void TravelCache::travel_to_waypoint(int num)
+{
+ if (num < 0 || num >= TRAVEL_WAYPOINT_COUNT) return;
+ if (waypoints[num].id.depth == -1) return;
+
+ travel_target = waypoints[num];
+ set_trans_travel_dest(trans_travel_dest, sizeof trans_travel_dest,
+ travel_target);
+ LevelInfo &li = get_level_info(travel_target.id);
+ li.travel_to_waypoint(travel_target.pos);
+}
+
+void TravelCache::list_waypoints() const
+{
+ std::string line;
+ char dest[30];
+ char choice[50];
+ int count = 0;
+
+ for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
+ {
+ if (waypoints[i].id.depth == -1) continue;
+
+ set_trans_travel_dest(dest, sizeof dest, waypoints[i]);
+ // All waypoints will have @ (x,y), remove that.
+ char *at = strchr(dest, '@');
+ if (at)
+ *--at = 0;
+
+ snprintf(choice, sizeof choice, "(%d) %-8s", i, dest);
+ line += choice;
+ if (!(++count % 5))
+ {
+ mpr(line.c_str());
+ line = "";
+ }
+ }
+ if (line.length())
+ mpr(line.c_str());
+}
+
+unsigned char TravelCache::is_waypoint(const level_pos &lp) const
+{
+ for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
+ {
+ if (lp == waypoints[i])
+ return '0' + i;
+ }
+ return 0;
+}
+
+void TravelCache::update_waypoints() const
+{
+ level_pos lp;
+ lp.id = level_id::get_current_level_id();
+
+ memset(curr_waypoints, 0, sizeof curr_waypoints);
+ for (lp.pos.x = 1; lp.pos.x < GXM; ++lp.pos.x)
+ {
+ for (lp.pos.y = 1; lp.pos.y < GYM; ++lp.pos.y)
+ {
+ unsigned char wpc = is_waypoint(lp);
+ if (wpc)
+ curr_waypoints[lp.pos.x][lp.pos.y] = wpc;
+ }
+ }
+}
+
+void TravelCache::add_waypoint(int x, int y)
+{
+ if (you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS
+ || you.level_type == LEVEL_PANDEMONIUM)
+ {
+ mpr("Sorry, you can't set a waypoint here.");
+ return;
+ }
+
+ mesclr();
+ if (get_waypoint_count())
+ {
+ mpr("Existing waypoints");
+ list_waypoints();
+ }
+
+ mpr("Assign waypoint to what number? (0-9) ", MSGCH_PROMPT);
+ int keyin = get_ch();
+
+ if (keyin < '0' || keyin > '9') return;
+
+ int waynum = keyin - '0';
+
+ if (waypoints[waynum].is_valid())
+ {
+ bool unique_waypoint = true;
+ for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
+ {
+ if (i == waynum) continue;
+ if (waypoints[waynum] == waypoints[i])
+ {
+ unique_waypoint = false;
+ break;
+ }
+ }
+
+ if (unique_waypoint)
+ {
+ LevelInfo &li = get_level_info(waypoints[waynum].id);
+ li.remove_waypoint(waypoints[waynum].pos);
+ }
+ }
+
+ if (x == -1 || y == -1)
+ {
+ x = you.x_pos;
+ y = you.y_pos;
+ }
+ coord_def pos = { x, y };
+ const level_id &lid = level_id::get_current_level_id();
+
+ LevelInfo &li = get_level_info(lid);
+ li.add_waypoint(pos);
+
+ waypoints[waynum].id = lid;
+ waypoints[waynum].pos = pos;
+
+ update_waypoints();
+}
+
+int TravelCache::get_waypoint_count() const
+{
+ int count = 0;
+ for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
+ if (waypoints[i].is_valid())
+ count++;
+ return count;
+}
+
+void TravelCache::reset_distances()
+{
+ std::map<level_id, LevelInfo, level_id::less_than>::iterator i =
+ levels.begin();
+ for ( ; i != levels.end(); ++i)
+ i->second.reset_distances();
+}
+
+bool TravelCache::is_known_branch(unsigned char branch) const
+{
+ std::map<level_id, LevelInfo, level_id::less_than>::const_iterator i =
+ levels.begin();
+ for ( ; i != levels.end(); ++i)
+ if (i->second.is_known_branch(branch))
+ return true;
+ return false;
+}
+
+void TravelCache::save(FILE *file) const
+{
+ // Travel cache version information
+ writeByte(file, TC_MAJOR_VERSION);
+ writeByte(file, TC_MINOR_VERSION);
+
+ // How many levels do we have?
+ writeShort(file, levels.size());
+
+ // Save all the levels we have
+ std::map<level_id, LevelInfo, level_id::less_than>::const_iterator i =
+ levels.begin();
+ for ( ; i != levels.end(); ++i)
+ {
+ i->first.save(file);
+ i->second.save(file);
+ }
+
+ for (int wp = 0; wp < TRAVEL_WAYPOINT_COUNT; ++wp)
+ waypoints[wp].save(file);
+}
+
+void TravelCache::load(FILE *file)
+{
+ levels.clear();
+
+ // Check version. If not compatible, we just ignore the file altogether.
+ unsigned char major = readByte(file),
+ minor = readByte(file);
+ if (major != TC_MAJOR_VERSION || minor != TC_MINOR_VERSION) return ;
+
+ int level_count = readShort(file);
+ for (int i = 0; i < level_count; ++i)
+ {
+ level_id id;
+ id.load(file);
+ LevelInfo linfo;
+ // Must set id before load, or travel_hell_entry will not be
+ // correctly set.
+ linfo.id = id;
+ linfo.load(file);
+ levels[id] = linfo;
+ }
+
+ for (int wp = 0; wp < TRAVEL_WAYPOINT_COUNT; ++wp)
+ waypoints[wp].load(file);
+
+ fixup_levels();
+}
+
+void TravelCache::set_level_excludes()
+{
+ if (can_travel_interlevel())
+ get_level_info(level_id::get_current_level_id()).set_level_excludes();
+}
+
+void TravelCache::update()
+{
+ if (can_travel_interlevel())
+ get_level_info(level_id::get_current_level_id()).update();
+ else
+ update_excludes();
+}
+
+void TravelCache::fixup_levels()
+{
+ std::map<level_id, LevelInfo, level_id::less_than>::iterator i =
+ levels.begin();
+ for ( ; i != levels.end(); ++i)
+ i->second.fixup();
+}
+
+bool can_travel_interlevel()
+{
+ return !(you.level_type == LEVEL_LABYRINTH || you.level_type == LEVEL_ABYSS
+ || you.level_type == LEVEL_PANDEMONIUM);
+}
diff --git a/trunk/source/travel.h b/trunk/source/travel.h
new file mode 100644
index 0000000000..e48a148a8c
--- /dev/null
+++ b/trunk/source/travel.h
@@ -0,0 +1,369 @@
+/*
+ * File: travel.cc
+ * Summary: Travel stuff
+ * Written by: Darshan Shaligram
+ *
+ * Change History (most recent first):
+ *
+ * <1> -/--/-- SD Created
+ */
+#ifndef TRAVEL_H
+# define TRAVEL_H
+
+# include "externs.h"
+# include <stdio.h>
+# include <string>
+# include <vector>
+# include <map>
+
+/* ***********************************************************************
+ * Initialises the travel subsystem.
+ *
+ * ***********************************************************************
+ * called from: initfile (what's a better place to initialise stuff?)
+ * *********************************************************************** */
+void initialise_travel();
+void stop_running(void);
+void travel_init_new_level();
+void toggle_exclude(int x, int y);
+void clear_excludes();
+unsigned char is_waypoint(int x, int y);
+void update_excludes();
+bool is_stair(unsigned gridc);
+bool is_travelable_stair(unsigned gridc);
+int stair_direction(int stair_feat);
+bool is_player_mapped(unsigned char envch);
+
+inline bool is_player_mapped(int grid_x, int grid_y)
+{
+ return (is_player_mapped( env.map[grid_x - 1][grid_y - 1] ));
+}
+
+/* ***********************************************************************
+ * Returns the direction to take to move along the shortest path between
+ * (you_x, you_y) and (you.run_x, you.run_y) in (*move_x, *move_y).
+ * If move_x or move_y is NULL, the function explores the map outwards from
+ * (you_x, you_y), populating the coords vector with the coordinates
+ * of every dungeon feature it finds, features closest to the character
+ * (travel distance-wise) coming first in the vector. A 'feature' is defined
+ * as a trap, and any other non-floor, non-water/lava square that the character
+ * can step onto.
+ *
+ * ***********************************************************************
+ * called from: acr - view
+ * *********************************************************************** */
+void find_travel_pos(int you_x, int you_y, char *move_x, char *move_y,
+ std::vector<coord_def>* coords = NULL);
+
+/* ***********************************************************************
+ * Initiates explore - the character runs around the level to map it. Note
+ * that the caller has to ensure that the level is mappable before calling
+ * start_explore. start_explore may lock up the game on unmappable levels.
+ *
+ * ***********************************************************************
+ * called from: acr
+ * *********************************************************************** */
+void start_explore();
+
+struct level_pos;
+void start_translevel_travel(const level_pos &pos);
+
+void start_translevel_travel(bool prompt_for_destination = true);
+
+void start_travel(int x, int y);
+
+void travel(int *keyin, char *move_x, char *move_y);
+
+int travel_direction(unsigned char branch, int subdungeondepth);
+
+void prevent_travel_to(const std::string &dungeon_feature_name);
+
+int subdungeon_depth(unsigned char branch, int depth);
+
+int absdungeon_depth(unsigned char branch, int subdepth);
+
+// Sort dungeon features as appropriate.
+void arrange_features(std::vector<coord_def> &features);
+
+// Magic numbers for point_distance:
+
+// This square is a trap
+#define PD_TRAP -42
+
+// The user never wants to travel this square
+#define PD_EXCLUDED -20099
+
+// This square is within LOS radius of an excluded square
+#define PD_EXCLUDED_RADIUS -20100
+
+// This square is a waypoint
+#define PD_WAYPOINT -20200
+
+/* ***********************************************************************
+ * Array of points on the map, each value being the distance the character
+ * would have to travel to get there. Negative distances imply that the point
+ * is a) a trap or hostile terrain or b) only reachable by crossing a trap or
+ * hostile terrain.
+ * ***********************************************************************
+ * referenced in: travel - view
+ * *********************************************************************** */
+extern short point_distance[GXM][GYM];
+
+// Possible values of you.running
+enum RUN_MODES
+{
+ RUN_TRAVEL = -1, // Classic or Plain Old travel
+ RUN_EXPLORE = -2, // Exploring (Ctrl+O)
+ RUN_INTERLEVEL = -3, // Interlevel travel (Ctrl+G)
+};
+
+enum EXPLORE_STOP
+{
+ ES_NONE = 0,
+ ES_ITEM = 1,
+ ES_STAIR = 2,
+ ES_SHOP = 4,
+ ES_ALTAR = 8,
+};
+
+////////////////////////////////////////////////////////////////////////////
+// Structs for interlevel travel.
+
+struct level_id
+{
+ unsigned char branch; // The branch in which the level is.
+ int depth; // What depth (in this branch - starting from 1)
+ // this level is.
+
+ level_id() : branch(0), depth(-1) { }
+
+ level_id(unsigned char br, int dep) : branch(br), depth(dep) { }
+
+ // Returns the level_id of the current level.
+ static level_id get_current_level_id();
+
+ // Returns the level_id of the level that the stair/portal/whatever at
+ // 'pos' on the current level leads to.
+ static level_id get_next_level_id(const coord_def &pos);
+
+ bool operator == ( const level_id &id ) const
+ {
+ return branch == id.branch && depth == id.depth;
+ }
+
+ bool operator != ( const level_id &id ) const
+ {
+ return branch != id.branch || depth != id.depth;
+ }
+
+ struct less_than
+ {
+ bool operator () (const level_id &first, const level_id &second) const
+ {
+ return first.branch < second.branch ||
+ (first.branch == second.branch && first.depth < second.depth);
+ }
+ };
+
+ void save(FILE *) const;
+ void load(FILE *);
+};
+
+// A position on a particular level.
+struct level_pos
+{
+ level_id id;
+ coord_def pos; // The grid coordinates on this level.
+
+ level_pos() : id(), pos()
+ {
+ pos.x = pos.y = -1;
+ }
+
+ level_pos(const level_id &lid, const coord_def &coord)
+ : id(lid), pos(coord)
+ {
+ }
+
+ level_pos(const level_id &lid)
+ : id(lid), pos()
+ {
+ pos.x = pos.y = -1;
+ }
+
+ bool operator == ( const level_pos &lp ) const
+ {
+ return id == lp.id && pos == lp.pos;
+ }
+
+ bool operator != ( const level_pos &lp ) const
+ {
+ return id != lp.id || pos != lp.pos;
+ }
+
+ bool is_valid() const
+ {
+ return id.depth > -1 && pos.x != -1 && pos.y != -1;
+ }
+
+ void save(FILE *) const;
+ void load(FILE *);
+};
+
+struct stair_info
+{
+ coord_def position; // Position of stair
+
+ level_pos destination; // The level and the position on the level this
+ // stair leads to. This may be a guess.
+
+ int distance; // The distance traveled to reach this stair.
+
+ bool guessed_pos; // true if we're not sure that 'destination' is
+ // correct.
+
+ stair_info() : destination(), distance(-1), guessed_pos(true)
+ {
+ position.x = position.y = -1;
+ }
+
+ void reset_distance()
+ {
+ distance = -1;
+ }
+
+ void save(FILE *) const;
+ void load(FILE *);
+};
+
+// Information on a level that interlevel travel needs.
+struct LevelInfo
+{
+ LevelInfo() : stairs()
+ {
+ stair_distances = NULL;
+ }
+ LevelInfo(const LevelInfo &li);
+
+ ~LevelInfo();
+
+ const LevelInfo &operator = (const LevelInfo &other);
+
+ void save(FILE *) const;
+ void load(FILE *);
+
+ std::vector<stair_info> &get_stairs()
+ {
+ return stairs;
+ }
+
+ stair_info *get_stair(int x, int y);
+ stair_info *get_stair(const coord_def &pos);
+ int get_stair_index(const coord_def &pos) const;
+
+ void reset_distances();
+ void set_level_excludes();
+
+ // Returns the travel distance between two stairs. If either stair is NULL,
+ // or does not exist in our list of stairs, returns 0.
+ int distance_between(const stair_info *s1, const stair_info *s2) const;
+
+ void update(); // Update LevelInfo to be correct for the
+ // current level.
+
+ // Updates/creates a StairInfo for the stair at (x, y) in grid coordinates
+ void update_stair(int x, int y, const level_pos &p, bool guess = false);
+
+ // Returns true if the given branch is known to be accessible from the
+ // current level.
+ bool is_known_branch(unsigned char branch) const;
+
+ void add_waypoint(const coord_def &pos);
+ void remove_waypoint(const coord_def &pos);
+
+ void travel_to_waypoint(const coord_def &pos);
+private:
+ // Gets a list of coordinates of all player-known stairs on the current
+ // level.
+ static void get_stairs(std::vector<coord_def> &stairs);
+
+ void correct_stair_list(const std::vector<coord_def> &s);
+ void update_stair_distances();
+ void fixup();
+
+private:
+ std::vector<stair_info> stairs;
+
+ // Squares that are not safe to travel to.
+ std::vector<coord_def> excludes;
+
+ short *stair_distances; // Distances between the various stairs
+ level_id id;
+
+ friend class TravelCache;
+};
+
+#define TRAVEL_WAYPOINT_COUNT 10
+// Tracks all levels that the player has seen.
+class TravelCache
+{
+public:
+ void reset_distances();
+
+ // Get the LevelInfo for the specified level (defaults to the current
+ // level).
+ LevelInfo& get_level_info(unsigned char branch = 0, int depth = -1)
+ {
+ return get_level_info( level_id(branch, depth) );
+ }
+
+ LevelInfo& get_level_info(const level_id &lev)
+ {
+ LevelInfo &li = levels[lev];
+ li.id = lev;
+ return li;
+ }
+
+ bool know_level(const level_id &lev) const
+ {
+ return levels.find(lev) != levels.end();
+ }
+
+ const level_pos &get_waypoint(int number) const
+ {
+ return waypoints[number];
+ }
+
+ int get_waypoint_count() const;
+
+ void set_level_excludes();
+
+ void add_waypoint(int x = -1, int y = -1);
+ unsigned char is_waypoint(const level_pos &lp) const;
+ void list_waypoints() const;
+ void travel_to_waypoint(int number);
+ void update_waypoints() const;
+
+
+ void update();
+
+ void save(FILE *) const;
+ void load(FILE *);
+
+ bool is_known_branch(unsigned char branch) const;
+
+private:
+ void fixup_levels();
+
+private:
+ std::map<level_id, LevelInfo, level_id::less_than> levels;
+ level_pos waypoints[TRAVEL_WAYPOINT_COUNT];
+};
+
+int level_distance(level_id first, level_id second);
+
+bool can_travel_interlevel();
+
+extern TravelCache travel_cache;
+
+#endif // TRAVEL_H
diff --git a/trunk/source/unrand.h b/trunk/source/unrand.h
index 595da409c0..ae38b07de8 100644
--- a/trunk/source/unrand.h
+++ b/trunk/source/unrand.h
@@ -463,7 +463,7 @@
0, 0
}
,
- "Many years ago it was property of powerful mage called "
+ "Many years ago it was the property of a powerful mage called "
"Boris. He got lost in the Dungeon while seeking the Orb. ",
"An ugly rusty dagger. ",
""
@@ -520,8 +520,8 @@
0, 0
}
,
- "It once belonged to one foreign god. It works best with some kind "
- "of special arrows which are not generally available.",
+ "It once belonged to a foreign god. It works best with "
+ "special arrows which are not generally available.",
"A wonderful golden bow. ",
""
}
@@ -593,7 +593,7 @@
0, 0
}
,
- "This weapon once belonged to Gar Dogh, the guard of king's treasures. "
+ "This weapon once belonged to Gar Dogh, the guard of a king's treasures. "
"According to legend he was lost somewhere in the Dungeon.",
"",
""
@@ -649,7 +649,7 @@
}
,
"",
- "A skin of some strange animal.",
+ "The skin of some strange animal.",
""
}
,
@@ -864,7 +864,7 @@
0, -30 // stealth
}
,
- "It's really dark and malign artifact and no wise man would even touch it.",
+ "It's a really dark and malign artifact and no wise man would even touch it.",
"",
""
}
@@ -882,9 +882,9 @@
0, 0
}
,
- "This trident was stolen many years ago from the Octopus's garden "
- "by one really unimportant and already dead man. But beware of "
- "Octopus's king's wrath!",
+ "This trident was stolen many years ago from the Octopus king's garden "
+ "by a really unimportant and already dead man. But beware of "
+ "the Octopus king's wrath!",
"",
""
}
@@ -938,7 +938,7 @@
0, 0
}
,
- "This powerful staff used to belong to leader of"
+ "This powerful staff used to belong to the leader of"
" the Guild of Five Elements.",
"A black glyphic staff.",
""
@@ -993,8 +993,8 @@
0, 50 // stealth
}
,
- "According to legend this robe was gift of Ratri the Goddess of the Night "
- "to one her follower.",
+ "According to legend this robe was the gift of Ratri the Goddess of the Night "
+ "to one of her followers.",
"A long black robe made of strange flossy material.",
""
}
@@ -1085,7 +1085,7 @@
}
,
"",
- "A gloves made of white silk.",
+ "A pair of gloves made of white silk.",
""
}
,
diff --git a/trunk/source/version.h b/trunk/source/version.h
index fce3d06539..583e965a6c 100644
--- a/trunk/source/version.h
+++ b/trunk/source/version.h
@@ -38,7 +38,7 @@
/* ***********************************************************************
* called from: chardump - command - newgame
* *********************************************************************** */
-#define VERSION "4.0.0 beta 26"
+#define VERSION "4.0.0 beta 26 (crawl-ref)"
// last updated 20feb2001 {GDL}
diff --git a/trunk/source/view.cc b/trunk/source/view.cc
index 8eea17e6a9..adfb1a62bd 100644
--- a/trunk/source/view.cc
+++ b/trunk/source/view.cc
@@ -34,6 +34,7 @@
#include "externs.h"
+#include "clua.h"
#include "debug.h"
#include "insult.h"
#include "macro.h"
@@ -44,7 +45,14 @@
#include "skills2.h"
#include "stuff.h"
#include "spells4.h"
+#include "stash.h"
+#include "travel.h"
+#if defined(DOS_TERM)
+typedef char screen_buffer_t;
+#else
+typedef unsigned short screen_buffer_t;
+#endif
unsigned char your_sign; // accessed as extern in transfor.cc and acr.cc
unsigned char your_colour; // accessed as extern in transfor.cc and acr.cc
@@ -56,8 +64,10 @@ extern int stealth; // defined in acr.cc
extern FixedVector<char, 10> Visible_Statue; // defined in acr.cc
// char colour_code_map(unsigned char map_value);
-char colour_code_map( int x, int y );
+screen_buffer_t colour_code_map( int x, int y, bool item_colour = false,
+ bool travel_colour = false );
+extern void (*viewwindow) (char, bool);
unsigned char (*mapch) (unsigned char);
unsigned char (*mapch2) (unsigned char);
unsigned char mapchar(unsigned char ldfk);
@@ -67,6 +77,9 @@ unsigned char mapchar4(unsigned char ldfk);
void cloud_grid(void);
void monster_grid(bool do_updates);
+static int display_glyph(int env_glyph);
+static int item_env_glyph(const item_def &item);
+
//---------------------------------------------------------------
//
// get_number_of_lines
@@ -538,6 +551,7 @@ static void get_ibm_symbol(unsigned int object, unsigned short *ch,
case 272:
*ch = '$';
+ *color = YELLOW;
break; // $ gold
case 273:
@@ -550,7 +564,6 @@ static void get_ibm_symbol(unsigned int object, unsigned short *ch,
}
} // end get_ibm_symbol()
-
//---------------------------------------------------------------
//
// viewwindow2
@@ -648,10 +661,21 @@ void viewwindow2(char draw_it, bool do_updates)
{
ASSERT(bufcount < BUFFER_SIZE);
- if (buffy[bufcount] != 0)
+ int mapx = count_x + you.x_pos - 9;
+ int mapy = count_y + you.y_pos - 9;
+ if (buffy[bufcount] != 0 && mapx >= 0 && mapx + 1 < GXM
+ && mapy >= 0 && mapy + 1 < GYM)
{
- env.map[ count_x + you.x_pos - 9 ]
- [ count_y + you.y_pos - 9 ] = buffy[bufcount];
+ unsigned short bch = buffy[bufcount];
+ if (mgrd[mapx + 1][mapy + 1] != NON_MONSTER) {
+ const monsters &m = menv[mgrd[mapx + 1][mapy + 1]];
+ if (!mons_is_mimic(m.type)
+ && mons_char(m.type) == bch)
+ {
+ bch |= mons_colour(m.type) << 12;
+ }
+ }
+ env.map[mapx][mapy] = bch;
}
if (Options.clean_map == 1
@@ -659,7 +683,9 @@ void viewwindow2(char draw_it, bool do_updates)
{
get_ibm_symbol(show_backup[count_x + 1][count_y + 1],
&ch, &color);
- env.map[ count_x + you.x_pos - 9 ]
+ if (mapx >= 0 && mapx < GXM
+ && mapy >= 0 && mapy < GYM)
+ env.map[ count_x + you.x_pos - 9 ]
[ count_y + you.y_pos - 9 ] = ch;
}
bufcount += 2;
@@ -695,18 +721,20 @@ void viewwindow2(char draw_it, bool do_updates)
ASSERT(bufcount + 1 < BUFFER_SIZE);
- buffy[bufcount] = env.map[ count_x + you.x_pos - 17 ]
+ buffy[bufcount] = (unsigned char)
+ env.map[ count_x + you.x_pos - 17 ]
[ count_y + you.y_pos - 9 ];
buffy[bufcount + 1] = DARKGREY;
if (Options.colour_map)
{
- if (env.map[ count_x + you.x_pos - 16 ]
- [ count_y + you.y_pos - 8 ] != 0)
+ if (env.map[ count_x + you.x_pos - 17 ]
+ [ count_y + you.y_pos - 9 ] != 0)
{
buffy[bufcount + 1]
= colour_code_map( count_x + you.x_pos - 17,
- count_y + you.y_pos - 9 );
+ count_y + you.y_pos - 9,
+ Options.item_colour );
}
}
bufcount += 2;
@@ -747,7 +775,7 @@ void viewwindow2(char draw_it, bool do_updates)
// following lines are purely optional.
// if used, players will 'jump' move.
// Resting will be a LOT faster too.
- if (you.running == 0)
+ if (you.running == 0 || (you.running < 0 && Options.travel_delay > -1))
{
for (count_x = 0; count_x < 1120; count_x += 2)
{ // 1056
@@ -769,18 +797,166 @@ void viewwindow2(char draw_it, bool do_updates)
}
} // end viewwindow2()
-char colour_code_map( int x, int y )
+static char get_travel_colour( int x, int y )
+{
+ if (is_waypoint(x + 1, y + 1))
+ return LIGHTGREEN;
+
+ short dist = point_distance[x + 1]
+ [y + 1];
+ return dist > 0 ? BLUE :
+ dist == PD_EXCLUDED ? LIGHTMAGENTA :
+ dist == PD_EXCLUDED_RADIUS ? RED :
+ dist < 0 ? CYAN :
+ DARKGREY;
+}
+
+#if defined(WIN32CONSOLE) || defined(DOS)
+static unsigned short dos_reverse_brand(unsigned short colour)
+{
+ if (Options.dos_use_background_intensity)
+ {
+ // If the console treats the intensity bit on background colours
+ // correctly, we can do a very simple colour invert.
+
+ // Special casery for shadows. Note this must be matched by the fix
+ // to libw32c.cc (the unpatched libw32c.cc does not draw spaces of any
+ // colour).
+ if (colour == BLACK)
+ colour = (DARKGREY << 4);
+ else
+ colour = (colour & 0xF) << 4;
+ }
+ else
+ {
+ // If we're on a console that takes its DOSness very seriously the
+ // background high-intensity bit is actually a blink bit. Blinking is
+ // evil, so we strip the background high-intensity bit. This, sadly,
+ // limits us to 7 background colours.
+
+ // Strip off high-intensity bit. Special case DARKGREY, since it's the
+ // high-intensity counterpart of black, and we don't want black on
+ // black.
+ //
+ // We *could* set the foreground colour to WHITE if the background
+ // intensity bit is set, but I think we've carried the
+ // angry-fruit-salad theme far enough already.
+
+ if (colour == DARKGREY)
+ colour |= (LIGHTGREY << 4);
+ else if (colour == BLACK)
+ colour = LIGHTGREY << 4;
+ else
+ {
+ // Zap out any existing background colour, and the high
+ // intensity bit.
+ colour &= 7;
+
+ // And swap the foreground colour over to the background
+ // colour, leaving the foreground black.
+ colour <<= 4;
+ }
+ }
+
+ return (colour);
+}
+
+static unsigned short dos_hilite_brand(unsigned short colour,
+ unsigned short hilite)
+{
+ if (!hilite)
+ return (colour);
+
+ if (colour == hilite)
+ colour = 0;
+
+ colour |= (hilite << 4);
+ return (colour);
+}
+
+static unsigned short dos_brand( unsigned short colour,
+ unsigned brand = CHATTR_REVERSE )
+{
+ if ((brand & CHATTR_ATTRMASK) == CHATTR_NORMAL)
+ return (colour);
+
+ colour &= 0xFF;
+
+ if ((brand & CHATTR_ATTRMASK) == CHATTR_HILITE)
+ return dos_hilite_brand(colour, (brand & CHATTR_COLMASK) >> 8);
+ else
+ return dos_reverse_brand(colour);
+
+}
+#endif
+
+screen_buffer_t colour_code_map( int x, int y, bool item_colour,
+ bool travel_colour )
{
// XXX: Yes, the map array and the grid array are off by one. -- bwr
- const int map_value = env.map[x][y];
+ const int map_value = (unsigned char) env.map[x][y];
+ const unsigned short map_flags = env.map[x][y] & ENVF_FLAGS;
const int grid_value = grd[x + 1][y + 1];
+ char tc = travel_colour? get_travel_colour(x, y) : DARKGREY;
+
+ if (map_flags & ENVF_DETECT_ITEM)
+ tc = Options.detected_item_colour;
+
+ if (map_flags & ENVF_DETECT_MONS) {
+ tc = Options.detected_monster_colour;
+ return (tc);
+ }
+
+ unsigned char ecol = ENVF_COLOR(map_flags);
+ if (ecol) {
+ unsigned rmc = Options.remembered_monster_colour & 0xFFFF;
+ if (rmc == 0xFFFF) // Use real colour
+ tc = ecol;
+ else if (rmc == 0) // Don't colour
+ ;
+ else
+ tc = rmc;
+ }
+
+ // XXX: [ds] If we've an important colour, override other feature
+ // colouring. Yes, this is hacky. Story of my life.
+ if (tc == LIGHTGREEN || tc == LIGHTMAGENTA)
+ return tc;
+
// XXX: Yeah, this is ugly, but until we have stored layers in the
// map we can't tell if we've seen a square, detected it, or just
// detected the item or monster on top... giving colour here will
// result in detect creature/item detecting features like stairs. -- bwr
- if (map_value != mapch2( grid_value ))
- return (DARKGREY);
+ if (map_value != mapch2( grid_value )) {
+ // If there's an item on this square, change colour to indicate
+ // that, iff the item's glyph matches map_value. XXX: Potentially
+ // abusable? -- ds
+ int item = igrd[x + 1][y + 1];
+ if (item_colour && item != NON_ITEM
+ && map_value == display_glyph(item_env_glyph(mitm[item])))
+ {
+ screen_buffer_t ic = mitm[item].colour;
+
+#if defined(WIN32CONSOLE) || defined(DOS) || defined(DOS_TERM)
+ if (mitm[item].link != NON_ITEM
+ && Options.heap_brand != CHATTR_NORMAL)
+ {
+ ic = dos_brand(ic, Options.heap_brand);
+ }
+#elif defined(USE_COLOUR_OPTS)
+ if (mitm[item].link != NON_ITEM )
+ {
+ ic |= COLFLAG_ITEM_HEAP;
+ }
+#endif
+ // If the item colour is the background colour, tweak it to WHITE
+ // instead to catch the player's eye.
+ return ic == tc? WHITE : ic;
+ }
+
+ return tc;
+ }
switch (grid_value)
{
@@ -838,7 +1014,7 @@ char colour_code_map( int x, int y )
case DNGN_STONE_STAIRS_UP_II:
case DNGN_STONE_STAIRS_UP_III:
case DNGN_ROCK_STAIRS_UP:
- return (BLUE);
+ return (GREEN);
case DNGN_ENTER_ORCISH_MINES:
case DNGN_ENTER_HIVE:
@@ -880,9 +1056,36 @@ char colour_code_map( int x, int y )
break;
}
- return (DARKGREY);
+ return tc;
}
+void clear_map()
+{
+ for (int y = 0; y < GYM - 1; ++y)
+ {
+ for (int x = 0; x < GXM - 1; ++x)
+ {
+ unsigned short envc = env.map[x][y];
+ if (!envc)
+ continue;
+
+ bool unmapped = (envc & ENVF_DETECTED) != 0;
+ // Discard flags at this point.
+ envc = (unsigned char) envc;
+
+ const unsigned char &grdc = grd[x + 1][y + 1];
+ if (envc == mapch(grdc) || envc == mapch2(grdc))
+ continue;
+
+ int item = igrd[x + 1][y + 1];
+ if (item != NON_ITEM
+ && envc == display_glyph(item_env_glyph(mitm[item])))
+ continue;
+
+ env.map[x][y] = unmapped? 0 : mapch2(grdc);
+ }
+ }
+}
void monster_grid(bool do_updates)
{
@@ -1008,21 +1211,31 @@ void monster_grid(bool do_updates)
}
else if (!mons_friendly( monster )
&& !mons_is_mimic( monster->type )
- && !mons_flag( monster->type, M_NO_EXP_GAIN )
- && you.running > 0)
+ && !mons_flag( monster->type, M_NO_EXP_GAIN ))
{
- // Friendly monsters, mimics, or harmless monsters
- // don't disturb the player's running/resting.
- //
- // Doing it this way causes players in run mode 2
- // to move one square, and in mode 1 to stop. This
- // means that the character will run one square if
- // a monster is in sight... we automatically jump
- // to zero if we're resting. -- bwr
- if (you.run_x == 0 && you.run_y == 0)
- you.running = 0;
- else
- you.running--;
+ interrupt_activity( AI_SEE_MONSTER );
+ if (you.running != 0
+#ifdef CLUA_BINDINGS
+ && clua.callbooleanfn(true, "ch_stop_run",
+ "M", monster)
+#endif
+ )
+ {
+ // Friendly monsters, mimics, or harmless monsters
+ // don't disturb the player's running/resting.
+ //
+ // Doing it this way causes players in run mode 2
+ // to move one square, and in mode 1 to stop. This
+ // means that the character will run one square if
+ // a monster is in sight... we automatically jump
+ // to zero if we're resting. -- bwr
+ if (you.run_x == 0 && you.run_y == 0)
+ stop_running();
+ else if (you.running > 1)
+ you.running--;
+ else
+ stop_running();
+ }
}
// mimics are always left on map
@@ -1048,6 +1261,59 @@ void monster_grid(bool do_updates)
[monster->y - you.y_pos + 9]
|= COLFLAG_FRIENDLY_MONSTER;
}
+ else if (Options.stab_brand != CHATTR_NORMAL
+ && !mons_is_mimic(monster->type)
+ && monster->type != MONS_OKLOB_PLANT
+ && mons_is_stabbable(monster))
+ {
+ env.show_col[monster->x - you.x_pos + 9]
+ [monster->y - you.y_pos + 9]
+ |= COLFLAG_WILLSTAB;
+ }
+ else if (Options.may_stab_brand != CHATTR_NORMAL
+ && !mons_is_mimic(monster->type)
+ && monster->type != MONS_OKLOB_PLANT
+ && mons_maybe_stabbable(monster))
+ {
+ env.show_col[monster->x - you.x_pos + 9]
+ [monster->y - you.y_pos + 9]
+ |= COLFLAG_MAYSTAB;
+ }
+
+#elif defined(WIN32CONSOLE) || defined(DOS)
+ if (Options.friend_brand != CHATTR_NORMAL
+ && mons_friendly(monster))
+ {
+ // We munge the colours right here for DOS and Windows, because
+ // we know exactly how the colours will be handled, and we don't
+ // want to change both DOS and Windows port code to handle
+ // friend branding.
+ unsigned short &colour =
+ env.show_col[monster->x - you.x_pos + 9]
+ [monster->y - you.y_pos + 9];
+ colour = dos_brand(colour, Options.friend_brand);
+ }
+
+ if (Options.stab_brand != CHATTR_NORMAL
+ && !mons_is_mimic(monster->type)
+ && monster->type != MONS_OKLOB_PLANT
+ && mons_is_stabbable(monster))
+ {
+ unsigned short &colour =
+ env.show_col[monster->x - you.x_pos + 9]
+ [monster->y - you.y_pos + 9];
+ colour = dos_brand(colour, Options.stab_brand);
+ }
+ else if (Options.may_stab_brand != CHATTR_NORMAL
+ && !mons_is_mimic(monster->type)
+ && monster->type != MONS_OKLOB_PLANT
+ && mons_maybe_stabbable(monster))
+ {
+ unsigned short &colour =
+ env.show_col[monster->x - you.x_pos + 9]
+ [monster->y - you.y_pos + 9];
+ colour = dos_brand(colour, Options.may_stab_brand);
+ }
#endif
} // end "if (monster->type != -1 && mons_ner)"
} // end "for s"
@@ -1114,6 +1380,56 @@ bool check_awaken(int mons_aw)
return (random2(stealth) <= mons_perc);
} // end check_awaken()
+static int display_glyph(int env_glyph)
+{
+ unsigned short ch, color;
+ if (viewwindow == viewwindow2)
+ get_ibm_symbol(env_glyph, &ch, &color);
+ else
+ get_non_ibm_symbol(env_glyph, &ch, &color);
+ return ch;
+}
+
+static int item_env_glyph(const item_def &item)
+{
+ switch (item.base_type)
+ {
+ case OBJ_ORBS:
+ return 256;
+ // need + 6 because show is 0 - 12, not -6 - +6
+ case OBJ_WEAPONS:
+ case OBJ_MISSILES:
+ return 258;
+ case OBJ_ARMOUR:
+ return 259;
+ case OBJ_WANDS:
+ return 260;
+ case OBJ_FOOD:
+ return 261;
+ case OBJ_UNKNOWN_I:
+ return 262;
+ case OBJ_SCROLLS:
+ return 263;
+ case OBJ_JEWELLERY:
+ return item.sub_type >= AMU_RAGE? 273 : 264;
+ case OBJ_POTIONS:
+ return 265;
+ case OBJ_UNKNOWN_II:
+ return 266;
+ case OBJ_BOOKS:
+ return 267;
+ case OBJ_STAVES:
+ return 269;
+ case OBJ_MISCELLANY:
+ return 270;
+ case OBJ_CORPSES:
+ return 271;
+ case OBJ_GOLD:
+ return 272;
+ default:
+ return '8';
+ }
+}
void item()
{
@@ -1130,102 +1446,31 @@ void item()
if (env.show[count_x - you.x_pos + 9]
[count_y - you.y_pos + 9] != 0)
{
- if (grd[count_x][count_y] == DNGN_SHALLOW_WATER)
- {
+ const item_def &eitem = mitm[igrd[count_x][count_y]];
+ unsigned short &ecol =
env.show_col[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = CYAN;
- }
- else
+ [count_y - you.y_pos + 9];
+
+ ecol = (grd[count_x][count_y] == DNGN_SHALLOW_WATER)?
+ CYAN
+ : eitem.colour;
+
+#ifdef USE_COLOUR_OPTS
+ if (eitem.link != NON_ITEM)
{
- env.show_col[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9]
- = mitm[igrd[count_x][count_y]].colour;
+ ecol |= COLFLAG_ITEM_HEAP;
}
-
- switch (mitm[igrd[count_x][count_y]].base_type)
+#elif defined(WIN32CONSOLE) || defined(DOS)
+ if (eitem.link != NON_ITEM
+ && Options.heap_brand != CHATTR_NORMAL)
{
- case OBJ_ORBS:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 256;
- break;
- // need + 6 because show is 0 - 12, not -6 - +6
- case OBJ_WEAPONS:
- case OBJ_MISSILES:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 258;
- break;
- case OBJ_ARMOUR:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 259;
- break;
- case OBJ_WANDS:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 260;
- break;
- case OBJ_FOOD:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 261;
- break;
- case OBJ_UNKNOWN_I:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 262;
- break;
- case OBJ_SCROLLS:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 263;
- break;
-
- case OBJ_JEWELLERY:
- if (mitm[igrd[count_x][count_y]].sub_type >= AMU_RAGE)
- {
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 273;
- }
- else
- {
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 264;
- }
- break;
-
- case OBJ_POTIONS:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 265;
- break;
- case OBJ_UNKNOWN_II:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 266;
- break;
- case OBJ_BOOKS:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 267;
- break;
- case OBJ_STAVES:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 269;
- break;
- case OBJ_MISCELLANY:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 270;
- break;
- case OBJ_CORPSES:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 271;
- break;
-
- case OBJ_GOLD:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = 272;
-
- env.show_col[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = YELLOW;
- break;
-
- default:
- env.show[count_x - you.x_pos + 9]
- [count_y - you.y_pos + 9] = '8';
- break;
+ // Yes, exact same code as friend-branding.
+ ecol = dos_brand(ecol, Options.heap_brand);
}
+#endif
+ env.show[count_x - you.x_pos + 9]
+ [count_y - you.y_pos + 9] =
+ item_env_glyph( eitem );
}
}
}
@@ -1741,6 +1986,226 @@ void draw_border(void)
gotoxy(40, 12); cprintf("Level");
} // end draw_border()
+// Determines if the given feature is present at (x, y) in _grid_ coordinates.
+// If you have map coords, add (1, 1) to get grid coords.
+// Use one of
+// 1. '<' and '>' to look for stairs
+// 2. '\t' or '\\' for shops, portals.
+// 3. '^' for traps
+// 4. '_' for altars
+// 5. Anything else will look for the exact same character in the level map.
+bool is_feature(int feature, int x, int y) {
+ unsigned char envfeat = (unsigned char) env.map[x - 1][y - 1];
+ if (!envfeat)
+ return false;
+
+ // 'grid' can fit in an unsigned char, but making this a short shuts up
+ // warnings about out-of-range case values.
+ short grid = grd[x][y];
+
+ switch (feature) {
+ case 'X':
+ return (point_distance[x][y] == PD_EXCLUDED);
+ case 'F':
+ case 'W':
+ return is_waypoint(x, y);
+#ifdef STASH_TRACKING
+ case 'I':
+ return is_stash(x, y);
+#endif
+ case '_':
+ switch (grid) {
+ case DNGN_ALTAR_ZIN:
+ case DNGN_ALTAR_SHINING_ONE:
+ case DNGN_ALTAR_KIKUBAAQUDGHA:
+ case DNGN_ALTAR_YREDELEMNUL:
+ case DNGN_ALTAR_XOM:
+ case DNGN_ALTAR_VEHUMET:
+ case DNGN_ALTAR_OKAWARU:
+ case DNGN_ALTAR_MAKHLEB:
+ case DNGN_ALTAR_SIF_MUNA:
+ case DNGN_ALTAR_TROG:
+ case DNGN_ALTAR_NEMELEX_XOBEH:
+ case DNGN_ALTAR_ELYVILON:
+ return true;
+ default:
+ return false;
+ }
+ case '\t':
+ case '\\':
+ switch (grid) {
+ case DNGN_ENTER_HELL:
+ case DNGN_ENTER_LABYRINTH:
+ case DNGN_ENTER_SHOP:
+ case DNGN_ENTER_DIS:
+ case DNGN_ENTER_GEHENNA:
+ case DNGN_ENTER_COCYTUS:
+ case DNGN_ENTER_TARTARUS:
+ case DNGN_ENTER_ABYSS:
+ case DNGN_EXIT_ABYSS:
+ case DNGN_STONE_ARCH:
+ case DNGN_ENTER_PANDEMONIUM:
+ case DNGN_EXIT_PANDEMONIUM:
+ case DNGN_TRANSIT_PANDEMONIUM:
+ case DNGN_ENTER_ZOT:
+ case DNGN_RETURN_FROM_ZOT:
+ return true;
+ default:
+ return false;
+ }
+ case '<':
+ switch (grid) {
+ case DNGN_ROCK_STAIRS_UP:
+ case DNGN_STONE_STAIRS_UP_I:
+ case DNGN_STONE_STAIRS_UP_II:
+ case DNGN_STONE_STAIRS_UP_III:
+ case DNGN_RETURN_FROM_ORCISH_MINES:
+ case DNGN_RETURN_FROM_HIVE:
+ case DNGN_RETURN_FROM_LAIR:
+ case DNGN_RETURN_FROM_SLIME_PITS:
+ case DNGN_RETURN_FROM_VAULTS:
+ case DNGN_RETURN_FROM_CRYPT:
+ case DNGN_RETURN_FROM_HALL_OF_BLADES:
+ case DNGN_RETURN_FROM_TEMPLE:
+ case DNGN_RETURN_FROM_SNAKE_PIT:
+ case DNGN_RETURN_FROM_ELVEN_HALLS:
+ case DNGN_RETURN_FROM_TOMB:
+ case DNGN_RETURN_FROM_SWAMP:
+ return true;
+ default:
+ return false;
+ }
+ case '>':
+ switch (grid) {
+ case DNGN_ROCK_STAIRS_DOWN:
+ case DNGN_STONE_STAIRS_DOWN_I:
+ case DNGN_STONE_STAIRS_DOWN_II:
+ case DNGN_STONE_STAIRS_DOWN_III:
+ case DNGN_ENTER_ORCISH_MINES:
+ case DNGN_ENTER_HIVE:
+ case DNGN_ENTER_LAIR:
+ case DNGN_ENTER_SLIME_PITS:
+ case DNGN_ENTER_VAULTS:
+ case DNGN_ENTER_CRYPT:
+ case DNGN_ENTER_HALL_OF_BLADES:
+ case DNGN_ENTER_TEMPLE:
+ case DNGN_ENTER_SNAKE_PIT:
+ case DNGN_ENTER_ELVEN_HALLS:
+ case DNGN_ENTER_TOMB:
+ case DNGN_ENTER_SWAMP:
+ return true;
+ default:
+ return false;
+ }
+ case '^':
+ switch (grid) {
+ case DNGN_TRAP_MECHANICAL:
+ case DNGN_TRAP_MAGICAL:
+ case DNGN_TRAP_III:
+ return true;
+ default:
+ return false;
+ }
+ default:
+ return envfeat == feature;
+ }
+}
+
+static int find_feature(unsigned char feature, int curs_x, int curs_y,
+ int start_x, int start_y, int anchor_x, int anchor_y,
+ int ignore_count, char *move_x, char *move_y) {
+ int cx = anchor_x,
+ cy = anchor_y;
+
+ int firstx = -1, firsty = -1;
+ int matchcount = 0;
+
+ // Find the first occurrence of feature 'feature', spiralling around (x,y)
+ int maxradius = GXM > GYM? GXM : GYM;
+ for (int radius = 1; radius < maxradius; ++radius) {
+ for (int axis = -2; axis < 2; ++axis) {
+ int rad = radius - (axis < 0);
+ for (int var = -rad; var <= rad; ++var) {
+ int dx = radius, dy = var;
+ if (axis % 2)
+ dx = -dx;
+ if (axis < 0) {
+ int temp = dx;
+ dx = dy;
+ dy = temp;
+ }
+
+ int x = cx + dx, y = cy + dy;
+ if (x < 0 || y < 0 || x >= GXM || y >= GYM) continue;
+ if (is_feature(feature, x + 1, y + 1)) {
+ ++matchcount;
+ if (!ignore_count--) {
+ // We want to cursor to (x,y)
+ *move_x = x - (start_x + curs_x - 1);
+ *move_y = y - (start_y + curs_y - 1);
+ return matchcount;
+ }
+ else if (firstx == -1) {
+ firstx = x;
+ firsty = y;
+ }
+ }
+ }
+ }
+ }
+
+ // We found something, but ignored it because of an ignorecount
+ if (firstx != -1) {
+ *move_x = firstx - (start_x + curs_x - 1);
+ *move_y = firsty - (start_y + curs_y - 1);
+ return 1;
+ }
+ return 0;
+}
+
+void find_features(const std::vector<coord_def>& features,
+ unsigned char feature, std::vector<coord_def> *found) {
+ for (unsigned feat = 0; feat < features.size(); ++feat) {
+ const coord_def& coord = features[feat];
+ if (is_feature(feature, coord.x, coord.y))
+ found->push_back(coord);
+ }
+}
+
+static int find_feature( const std::vector<coord_def>& features,
+ unsigned char feature, int curs_x, int curs_y,
+ int start_x, int start_y,
+ int ignore_count, char *move_x, char *move_y) {
+ int firstx = -1, firsty = -1;
+ int matchcount = 0;
+
+ for (unsigned feat = 0; feat < features.size(); ++feat) {
+ const coord_def& coord = features[feat];
+
+ if (is_feature(feature, coord.x, coord.y)) {
+ ++matchcount;
+ if (!ignore_count--) {
+ // We want to cursor to (x,y)
+ *move_x = coord.x - (start_x + curs_x);
+ *move_y = coord.y - (start_y + curs_y);
+ return matchcount;
+ }
+ else if (firstx == -1) {
+ firstx = coord.x;
+ firsty = coord.y;
+ }
+ }
+ }
+
+ // We found something, but ignored it because of an ignorecount
+ if (firstx != -1) {
+ *move_x = firstx - (start_x + curs_x);
+ *move_y = firsty - (start_y + curs_y);
+ return 1;
+ }
+ return 0;
+}
+
// show_map() now centers the known map along x or y. This prevents
// the player from getting "artificial" location clues by using the
// map to see how close to the end they are. They'll need to explore
@@ -1759,8 +2224,18 @@ void show_map( FixedVector<int, 2> &spec_place )
char buffer[4800];
#endif
+ // Vector to track all features we can travel to, in order of distance.
+ std::vector<coord_def> features;
+ if (!spec_place[0]) {
+ travel_cache.update();
+
+ find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, &features);
+ // Sort features into the order the player is likely to prefer.
+ arrange_features(features);
+ }
+
// buffer2[GYM * GXM * 2] segfaults my box {dlb}
- char buffer2[GYM * GXM * 2];
+ screen_buffer_t buffer2[GYM * GXM * 2];
char min_x = 80, max_x = 0, min_y = 0, max_y = 0;
bool found_y = false;
@@ -1794,6 +2269,7 @@ void show_map( FixedVector<int, 2> &spec_place )
const int map_lines = max_y - min_y + 1;
const int start_x = min_x + (max_x - min_x + 1) / 2 - 40; // no x scrolling
+ const int block_step = Options.level_map_cursor_step;
int start_y; // y does scroll
int screen_y = you.y_pos;
@@ -1815,6 +2291,7 @@ void show_map( FixedVector<int, 2> &spec_place )
int curs_x = you.x_pos - start_x;
int curs_y = you.y_pos - screen_y + half_screen;
+ int search_feat = 0, search_found = 0, anchor_x = -1, anchor_y = -1;
#ifdef DOS_TERM
gettext(1, 1, 80, 25, buffer);
@@ -1839,6 +2316,7 @@ void show_map( FixedVector<int, 2> &spec_place )
{
for (i = 0; i < 80; i++)
{
+ screen_buffer_t colour = DARKGREY;
if (start_y + j >= 65 || start_y + j <= 3
|| start_x + i < 0 || start_x + i >= GXM - 1)
{
@@ -1856,12 +2334,32 @@ void show_map( FixedVector<int, 2> &spec_place )
}
- buffer2[bufcount2 + 1] = colour_code_map(start_x + i, start_y + j);
+ colour = colour_code_map(start_x + i, start_y + j,
+ Options.item_colour,
+ !spec_place[0] && Options.travel_colour);
+
+ buffer2[bufcount2 + 1] = colour;
if (start_x + i + 1 == you.x_pos && start_y + j + 1 == you.y_pos)
buffer2[bufcount2 + 1] = WHITE;
- buffer2[bufcount2] = env.map[start_x + i][start_y + j];
+ buffer2[bufcount2] =
+ (unsigned char) env.map[start_x + i][start_y + j];
+
+ // If we've a waypoint on the current square, *and* the square is
+ // a normal floor square with nothing on it, show the waypoint
+ // number.
+ if (Options.show_waypoints)
+ {
+ // XXX: This is a horrible hack.
+ screen_buffer_t &bc = buffer2[bufcount2];
+ int gridx = start_x + i + 1, gridy = start_y + j + 1;
+ unsigned char ch = is_waypoint(gridx, gridy);
+ if (ch && (bc == mapch2(DNGN_FLOOR) ||
+ bc == mapch(DNGN_FLOOR)))
+ bc = ch;
+ }
+
bufcount2 += 2;
#ifdef PLAIN_TERM
@@ -1889,16 +2387,26 @@ void show_map( FixedVector<int, 2> &spec_place )
gotoxy(curs_x, curs_y);
gettything:
- getty = getch();
+ getty = getchm(KC_LEVELMAP);
if (spec_place[0] == 0 && getty != 0 && getty != '+' && getty != '-'
&& getty != 'h' && getty != 'j' && getty != 'k' && getty != 'l'
&& getty != 'y' && getty != 'u' && getty != 'b' && getty != 'n'
-#ifdef UNIX
&& getty != 'H' && getty != 'J' && getty != 'K' && getty != 'L'
&& getty != 'Y' && getty != 'U' && getty != 'B' && getty != 'N'
-#endif
- && (getty < '0' || getty > '9'))
+ // Keystrokes to initiate travel
+ && getty != ',' && getty != '.' && getty != '\r' && getty != ';'
+
+ // Keystrokes for jumping to features
+ && getty != '<' && getty != '>' && getty != '@' && getty != '\t'
+ && getty != '^' && getty != '_'
+ && (getty < '0' || getty > '9')
+ && getty != CONTROL('X')
+ && getty != CONTROL('E')
+ && getty != CONTROL('F')
+ && getty != CONTROL('W')
+ && getty != CONTROL('C')
+ && getty != 'X' && getty != 'F' && getty != 'I' && getty != 'W')
{
goto putty;
}
@@ -1906,20 +2414,67 @@ void show_map( FixedVector<int, 2> &spec_place )
if (spec_place[0] == 1 && getty != 0 && getty != '+' && getty != '-'
&& getty != 'h' && getty != 'j' && getty != 'k' && getty != 'l'
&& getty != 'y' && getty != 'u' && getty != 'b' && getty != 'n'
-#ifdef UNIX
&& getty != 'H' && getty != 'J' && getty != 'K' && getty != 'L'
&& getty != 'Y' && getty != 'U' && getty != 'B' && getty != 'N'
-#endif
- && getty != '.' && getty != 'S' && (getty < '0' || getty > '9'))
+ && getty != '.' && getty != 'S' && (getty < '0' || getty > '9')
+ // Keystrokes for jumping to features
+ && getty != '<' && getty != '>' && getty != '@' && getty != '\t'
+ && getty != '^' && getty != '_')
{
goto gettything;
}
if (getty == 0)
- getty = getch();
+ getty = getchm(KC_LEVELMAP);
+
+#ifdef WIN32CONSOLE
+ // Translate shifted numpad to shifted vi keys. Yes,
+ // this is horribly hacky.
+ {
+ static int win_keypad[] = { 'B', 'J', 'N',
+ 'H', '5', 'L',
+ 'Y', 'K', 'U' };
+ if (getty >= '1' && getty <= '9')
+ getty = win_keypad[ getty - '1' ];
+ }
+#endif
switch (getty)
{
+ case CONTROL('C'):
+ clear_map();
+ break;
+
+ case CONTROL('F'):
+ case CONTROL('W'):
+ travel_cache.add_waypoint(start_x + curs_x, start_y + curs_y);
+ // We need to do this all over again so that the user can jump
+ // to the waypoint he just created.
+ features.clear();
+ find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, &features);
+ // Sort features into the order the player is likely to prefer.
+ arrange_features(features);
+ move_x = move_y = 0;
+ break;
+ case CONTROL('E'):
+ case CONTROL('X'):
+ {
+ int x = start_x + curs_x, y = start_y + curs_y;
+ if (getty == CONTROL('X'))
+ toggle_exclude(x, y);
+ else
+ clear_excludes();
+
+ // We now need to redo travel colours
+ features.clear();
+ find_travel_pos(you.x_pos, you.y_pos, NULL, NULL, &features);
+ // Sort features into the order the player is likely to prefer.
+ arrange_features(features);
+
+ move_x = move_y = 0;
+ }
+ break;
+
case 'b':
case '1':
move_x = -1;
@@ -1968,85 +2523,46 @@ void show_map( FixedVector<int, 2> &spec_place )
move_y = 0;
break;
-#ifndef UNIX
- // This is old DOS keypad support
- case 'H':
- move_y = -1;
- move_x = 0;
- break;
- case 'P':
- move_y = 1;
- move_x = 0;
- break;
- case 'K':
- move_x = -1;
- move_y = 0;
- break;
- case 'M':
- move_x = 1;
- move_y = 0;
- break;
- case 'O':
- move_x = -1;
- move_y = 1;
- break;
- case 'I':
- move_x = 1;
- move_y = -1;
- break;
- case 'G':
- move_y = -1;
- move_x = -1;
- break;
- case 'Q':
- move_y = 1;
- move_x = 1;
- break;
-
-#else
-
case 'B':
- move_x = -10;
- move_y = 10;
+ move_x = -block_step;
+ move_y = block_step;
break;
case 'J':
- move_y = 10;
+ move_y = block_step;
move_x = 0;
break;
case 'U':
- move_x = 10;
- move_y = -10;
+ move_x = block_step;
+ move_y = -block_step;
break;
case 'K':
- move_y = -10;
+ move_y = -block_step;
move_x = 0;
break;
case 'Y':
- move_y = -10;
- move_x = -10;
+ move_y = -block_step;
+ move_x = -block_step;
break;
case 'H':
- move_x = -10;
+ move_x = -block_step;
move_y = 0;
break;
case 'N':
- move_y = 10;
- move_x = 10;
+ move_y = block_step;
+ move_x = block_step;
break;
case 'L':
- move_x = 10;
+ move_x = block_step;
move_y = 0;
break;
-#endif
-
case '+':
move_y = 20;
move_x = 0;
@@ -2055,13 +2571,57 @@ void show_map( FixedVector<int, 2> &spec_place )
move_y = -20;
move_x = 0;
break;
+ case '<':
+ case '>':
+ case '@':
+ case '\t':
+ case '^':
+ case '_':
+ case 'X':
+ case 'F':
+ case 'W':
+ case 'I':
+ move_x = 0;
+ move_y = 0;
+ if (anchor_x == -1) {
+ anchor_x = start_x + curs_x - 1;
+ anchor_y = start_y + curs_y - 1;
+ }
+ if (search_feat != getty) {
+ search_feat = getty;
+ search_found = 0;
+ }
+ if (!spec_place[0])
+ search_found = find_feature(features, getty, curs_x, curs_y,
+ start_x, start_y,
+ search_found, &move_x, &move_y);
+ else
+ search_found = find_feature(getty, curs_x, curs_y,
+ start_x, start_y,
+ anchor_x, anchor_y,
+ search_found, &move_x, &move_y);
+ break;
case '.':
case '\r':
case 'S':
- spec_place[0] = start_x + curs_x;
- spec_place[1] = start_y + curs_y;
- goto putty;
-
+ case ',':
+ case ';':
+ {
+ int x = start_x + curs_x, y = start_y + curs_y;
+ if (!spec_place[0] && x == you.x_pos && y == you.y_pos)
+ {
+ if (you.travel_x > 0 && you.travel_y > 0) {
+ move_x = you.travel_x - x;
+ move_y = you.travel_y - y;
+ }
+ break;
+ }
+ else {
+ spec_place[0] = x;
+ spec_place[1] = y;
+ goto putty;
+ }
+ }
default:
move_x = 0;
move_y = 0;
@@ -2099,10 +2659,12 @@ void show_map( FixedVector<int, 2> &spec_place )
// screen_y += (curs_y + move_y) - 1;
screen_y += move_y;
- if (screen_y < min_y + half_screen)
+ if (screen_y < min_y + half_screen) {
+ move_y = screen_y - (min_y + half_screen);
screen_y = min_y + half_screen;
-
- move_y = 0;
+ }
+ else
+ move_y = 0;
}
if (curs_y + move_y > num_lines - 1)
@@ -2110,10 +2672,12 @@ void show_map( FixedVector<int, 2> &spec_place )
// screen_y += (curs_y + move_y) - num_lines + 1;
screen_y += move_y;
- if (screen_y > max_y - half_screen)
+ if (screen_y > max_y - half_screen) {
+ move_y = screen_y - (max_y - half_screen);
screen_y = max_y - half_screen;
-
- move_y = 0;
+ }
+ else
+ move_y = 0;
}
}
@@ -2150,7 +2714,9 @@ void magic_mapping(int map_radius, int proportion)
if (i < 5 || j < 5 || i > (GXM - 5) || j > (GYM - 5))
continue;
- if (env.map[i][j] == mapch2(grd[i + 1][j + 1]))
+ //if (env.map[i][j] == mapch2(grd[i + 1][j + 1]))
+ // continue;
+ if (env.map[i][j])
continue;
empty_count = 8;
@@ -2551,7 +3117,7 @@ bool mons_near(struct monsters *monster, unsigned int foe)
// without the IBM graphics option.
//
//---------------------------------------------------------------
-static void get_non_ibm_symbol(unsigned int object, unsigned short *ch,
+void get_non_ibm_symbol(unsigned int object, unsigned short *ch,
unsigned short *color)
{
ASSERT(color != NULL);
@@ -2981,6 +3547,7 @@ static void get_non_ibm_symbol(unsigned int object, unsigned short *ch,
case 272:
*ch = '$';
+ *color = YELLOW;
break; // $ gold
case 273:
@@ -3076,24 +3643,32 @@ void viewwindow3(char draw_it, bool do_updates)
bufcount += 16;
for (count_x = 0; count_x < 17; count_x++)
{
- if (buffy[bufcount] != 0
- && (count_x + you.x_pos - 9) >= 0
- && (count_y + you.y_pos - 9) >= 0)
+ int enx = count_x + you.x_pos - 9,
+ eny = count_y + you.y_pos - 9;
+ if (buffy[bufcount] != 0 && enx >= 0 && eny >= 0
+ && enx + 1 < GXM && eny + 1 < GYM)
{
- env.map[count_x + you.x_pos - 9]
- [count_y + you.y_pos - 9] = buffy[bufcount];
+ unsigned short bch = buffy[bufcount];
+ if (mgrd[enx + 1][eny + 1] != NON_MONSTER) {
+ const monsters &m = menv[ mgrd[enx + 1][eny + 1] ];
+ if (!mons_is_mimic(m.type)
+ && mons_char(m.type) == bch)
+ {
+ bch |= mons_colour(m.type) << 12;
+ }
+ }
+ env.map[enx][eny] = bch;
}
if (Options.clean_map == 1
&& show_backup[count_x + 1][count_y + 1] != 0
- && (count_x + you.x_pos - 9) >= 0
- && (count_y + you.y_pos - 9) >= 0)
+ && enx >= 0
+ && eny >= 0)
{
get_non_ibm_symbol( show_backup[count_x + 1]
[count_y + 1],
&ch, &color );
- env.map[count_x + you.x_pos - 9]
- [count_y + you.y_pos - 9] = ch;
+ env.map[enx][eny] = ch;
}
bufcount += 2;
}
@@ -3126,19 +3701,21 @@ void viewwindow3(char draw_it, bool do_updates)
continue;
}
- buffy[bufcount] = env.map[count_x + you.x_pos - 17]
+ buffy[bufcount] = (unsigned char)
+ env.map[count_x + you.x_pos - 17]
[count_y + you.y_pos - 9];
buffy[bufcount + 1] = DARKGREY;
if (Options.colour_map)
{
- if (env.map[count_x + you.x_pos - 16]
- [count_y + you.y_pos - 8] != 0)
+ if (env.map[count_x + you.x_pos - 17]
+ [count_y + you.y_pos - 9] != 0)
{
buffy[bufcount + 1]
= colour_code_map( count_x + you.x_pos - 17,
- count_y + you.y_pos - 9 );
+ count_y + you.y_pos - 9,
+ Options.item_colour );
}
}
@@ -3175,7 +3752,8 @@ void viewwindow3(char draw_it, bool do_updates)
gotoxy(2, 1);
bufcount = 0;
- if (you.running == 0) // this line is purely optional
+ // this line is purely optional
+ if (you.running == 0 || (you.running < 0 && Options.travel_delay > -1))
{
for (count_x = 0; count_x < 1120; count_x += 2) // 1056
{
diff --git a/trunk/source/view.h b/trunk/source/view.h
index 75c5714ddd..b36ff18551 100644
--- a/trunk/source/view.h
+++ b/trunk/source/view.h
@@ -21,6 +21,12 @@
int get_number_of_lines(void);
+/* ***********************************************************************
+ * called from: dump_screenshot - chardump
+ * *********************************************************************** */
+void get_non_ibm_symbol(unsigned int object, unsigned short *ch,
+ unsigned short *color);
+
// last updated 29may2000 {dlb}
/* ***********************************************************************
* called from: bang - beam - direct - effects - fight - monstuff -
@@ -41,6 +47,8 @@ void draw_border(void);
* *********************************************************************** */
void item(void);
+void find_features(const std::vector<coord_def>& features,
+ unsigned char feature, std::vector<coord_def> *found);
// last updated 12may2000 {dlb}
/* ***********************************************************************
@@ -96,5 +104,8 @@ void setLOSRadius(int newLR);
* *********************************************************************** */
bool check_awaken(int mons_aw);
+void clear_map();
+
+bool is_feature(int feature, int x, int y);
#endif