diff options
author | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2006-08-02 12:54:15 +0000 |
---|---|---|
committer | dshaligram <dshaligram@c06c8d41-db1a-0410-9941-cceddc491573> | 2006-08-02 12:54:15 +0000 |
commit | d5e5340c3926d1cf97f6cba151ffaecb20bfb35f (patch) | |
tree | d1faf7d5b27df8f3c523a8dd33357804118e62b1 /trunk/source/stash.cc | |
parent | 7b2204d69f21d7075e4666ee032d7a129081bc4b (diff) | |
download | crawl-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/source/stash.cc')
-rw-r--r-- | trunk/source/stash.cc | 1606 |
1 files changed, 1606 insertions, 0 deletions
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 ¤t = 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 ¤t = 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 = <pat; +#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); +} |