/* * File: invent.cc * Summary: Functions for inventory related commands. * Written by: Linley Henzell */ #include "AppHdr.h" #include "invent.h" #include #include #include #include #include #include "externs.h" #include "options.h" #include "artefact.h" #include "clua.h" #include "colour.h" #include "decks.h" #include "describe.h" #include "env.h" #include "food.h" #include "initfile.h" #include "item_use.h" #include "itemprop.h" #include "items.h" #include "macro.h" #include "message.h" #include "player.h" #include "shopping.h" #include "showsymb.h" #include "stuff.h" #include "menu.h" #include "mon-util.h" #include "state.h" #ifdef USE_TILE #include "tiles.h" #include "tiledef-main.h" #include "tiledef-dngn.h" #endif /////////////////////////////////////////////////////////////////////////////// // Inventory menu shenanigans static void _get_inv_items_to_show( std::vector &v, int selector, int excluded_slot = -1); InvTitle::InvTitle( Menu *mn, const std::string &title, invtitle_annotator tfn ) : MenuEntry( title, MEL_TITLE ) { m = mn; titlefn = tfn; } std::string InvTitle::get_text() const { return (titlefn ? titlefn( m, MenuEntry::get_text() ) : MenuEntry::get_text()); } InvEntry::InvEntry( const item_def &i ) : MenuEntry( "", MEL_ITEM ), item( &i ) { data = const_cast( item ); if (in_inventory(i) && i.base_type != OBJ_GOLD) { // We need to do this in order to get the 'wielded' annotation. // We then toss out the first four characters, which look // like this: "a - ". Ow. FIXME. text = i.name(DESC_INVENTORY_EQUIP, false).substr(4); } else text = i.name(DESC_NOCAP_A, false); if (i.base_type != OBJ_GOLD && in_inventory(i)) add_hotkey(index_to_letter( i.link )); else add_hotkey(' '); // dummy hotkey add_class_hotkeys(i); quantity = i.quantity; } const std::string &InvEntry::get_basename() const { if (basename.empty()) basename = item->name(DESC_BASENAME); return (basename); } const std::string &InvEntry::get_qualname() const { if (qualname.empty()) qualname = item->name(DESC_QUALNAME); return (qualname); } const std::string &InvEntry::get_fullname() const { return (text); } bool InvEntry::is_item_cursed() const { return (item_ident(*item, ISFLAG_KNOW_CURSE) && item->cursed()); } bool InvEntry::is_item_glowing() const { return (!item_ident(*item, ISFLAG_KNOW_TYPE) && (get_equip_desc(*item) || (is_artefact(*item) && (item->base_type == OBJ_WEAPONS || item->base_type == OBJ_MISSILES || item->base_type == OBJ_ARMOUR || item->base_type == OBJ_BOOKS)))); } bool InvEntry::is_item_ego() const { return (item_ident(*item, ISFLAG_KNOW_TYPE) && !is_artefact(*item) && item->special != 0 && (item->base_type == OBJ_WEAPONS || item->base_type == OBJ_MISSILES || item->base_type == OBJ_ARMOUR)); } bool InvEntry::is_item_art() const { return (item_ident(*item, ISFLAG_KNOW_TYPE) && is_artefact(*item)); } bool InvEntry::is_item_equipped() const { if (item->link == -1 || item->pos.x != -1 || item->pos.y != -1) return(false); for (int i = 0; i < NUM_EQUIP; i++) if (item->link == you.equip[i]) return (true); return (false); } // Returns values < 0 for edible chunks (non-rotten except for Saprovores), // 0 for non-chunks, and values > 0 for rotten chunks for non-Saprovores. int InvEntry::item_freshness() const { if (item->base_type != OBJ_FOOD || item->sub_type != FOOD_CHUNK) return 0; int freshness = item->special; if (freshness >= 100 || player_mutation_level(MUT_SAPROVOROUS)) freshness -= 300; // Ensure that chunk freshness is never zero, since zero means // that the item isn't a chunk. if (freshness >= 0) // possibly rotten chunks freshness++; return freshness; } void InvEntry::select(int qty) { if (item && item->quantity < qty) qty = item->quantity; MenuEntry::select(qty); } std::string InvEntry::get_filter_text() const { return (filtering_item_prefix(*item) + " " + get_text()); } std::string InvEntry::get_text() const { std::ostringstream tstr; tstr << ' ' << static_cast(hotkeys[0]) << ' '; if (!selected_qty) tstr << '-'; else if (selected_qty < quantity) tstr << '#'; else tstr << '+'; if (InvEntry::show_glyph) { glyph g = get_item_glyph(item); const std::string col_string = colour_to_str(g.col); const std::string prefix = " (<" + col_string + ">" + static_cast(g.ch) + ")"; tstr << prefix; } tstr << ' ' << text; if (InvEntry::show_prices) { const int value = item_value(*item, show_prices); if (value > 0) tstr << " (" << value << " gold)"; } if (Options.show_inventory_weights) { const int mass = item_mass(*item) * item->quantity; tstr << std::setw(get_number_of_cols() - tstr.str().length() - 2) << std::right << make_stringf("(%.1f aum)", BURDEN_TO_AUM * mass); } return tstr.str(); } static void _get_class_hotkeys(const int type, std::vector &glyphs) { switch (type) { case OBJ_GOLD: glyphs.push_back('$'); break; case OBJ_MISSILES: glyphs.push_back('('); break; case OBJ_WEAPONS: glyphs.push_back(')'); break; case OBJ_ARMOUR: glyphs.push_back('['); break; case OBJ_WANDS: glyphs.push_back('/'); break; case OBJ_FOOD: glyphs.push_back('%'); break; case OBJ_BOOKS: glyphs.push_back('+'); glyphs.push_back(':'); break; case OBJ_SCROLLS: glyphs.push_back('?'); break; case OBJ_JEWELLERY: glyphs.push_back('"'); glyphs.push_back('='); break; case OBJ_POTIONS: glyphs.push_back('!'); break; case OBJ_STAVES: glyphs.push_back('\\'); glyphs.push_back('|'); break; case OBJ_MISCELLANY: glyphs.push_back('}'); break; case OBJ_CORPSES: glyphs.push_back('&'); break; default: break; } } void InvEntry::add_class_hotkeys(const item_def &i) { const int type = i.base_type; if (type == OBJ_JEWELLERY) { add_hotkey(i.sub_type >= AMU_FIRST_AMULET ? '"' : '='); return; } std::vector glyphs; _get_class_hotkeys(type, glyphs); for (unsigned int k = 0; k < glyphs.size(); ++k) add_hotkey(glyphs[k]); // Hack to make rotten chunks answer to '&' as well. // Check for uselessness rather than inedibility to cover the spells // that use chunks. if (i.base_type == OBJ_FOOD && i.sub_type == FOOD_CHUNK && is_useless_item(i)) { add_hotkey('&'); } } bool InvEntry::show_prices = false; void InvEntry::set_show_prices(bool doshow) { show_prices = doshow; } InvShowPrices::InvShowPrices(bool doshow) { InvEntry::set_show_prices(doshow); } InvShowPrices::~InvShowPrices() { InvEntry::set_show_prices(false); } bool InvEntry::show_glyph = false; void InvEntry::set_show_glyph(bool doshow) { show_glyph = doshow; } InvMenu::InvMenu(int mflags) : Menu(mflags, "inventory", false), type(MT_INVLIST), pre_select(NULL), title_annotate(NULL) { #ifdef USE_TILE if (Options.tile_menu_icons) #endif mdisplay->set_num_columns(2); } // Returns vector of item_def pointers to each item_def in the given // vector. Note: make sure the original vector stays around for the lifetime // of the use of the item pointers, or mayhem results! std::vector InvMenu::xlat_itemvect(const std::vector &v) { std::vector xlatitems; for (unsigned i = 0, size = v.size(); i < size; ++i) xlatitems.push_back( &v[i] ); return (xlatitems); } void InvMenu::set_type(menu_type t) { type = t; } void InvMenu::set_title_annotator(invtitle_annotator afn) { title_annotate = afn; } void InvMenu::set_title(MenuEntry *t, bool first) { Menu::set_title(t, first); } void InvMenu::set_preselect(const std::vector *pre) { pre_select = pre; } void InvMenu::set_title(const std::string &s) { std::string stitle = s; if (stitle.empty()) { std::ostringstream default_title; const int cap = carrying_capacity(BS_UNENCUMBERED); default_title << make_stringf( "Inventory: %.0f/%.0f aum (%d%%, %d/52 slots)", BURDEN_TO_AUM * you.burden, BURDEN_TO_AUM * cap, (you.burden * 100) / cap, inv_count() ); default_title << std::setw(get_number_of_cols() - default_title.str().length() - 1) << std::right << "Press item letter to examine."; stitle = default_title.str(); } set_title(new InvTitle(this, stitle, title_annotate)); } static bool _has_tran_unwearable_armour(bool &melded) { bool unwearable = false; melded = false; for (int i = 0; i < ENDOFPACK; i++) { item_def &item(you.inv[i]); if (item.is_valid() && item.base_type == OBJ_ARMOUR && !you_tran_can_wear(item)) { unwearable = true; if (item_is_equipped(item)) { melded = true; return (true); } } } return (unwearable); } static std::string _no_selectables_message(int item_selector) { switch (item_selector) { case OSEL_ANY: return("You aren't carrying anything."); case OSEL_WIELD: case OBJ_WEAPONS: return("You aren't carrying any weapons."); case OBJ_ARMOUR: { bool melded = false; if (_has_tran_unwearable_armour(melded)) { if (melded) return ("Your armour is currently melded into you."); else return("You aren't carrying any armour you can wear in your " "current form."); } else return("You aren't carrying any armour."); } case OSEL_UNIDENT: return("You don't have any unidentified items."); case OSEL_RECHARGE: return("You aren't carrying any rechargeable items."); case OSEL_ENCH_ARM: return("You aren't carrying any armour which can be enchanted " "further."); case OBJ_CORPSES: case OSEL_VAMP_EAT: return("You aren't carrying any corpses which you can drain."); case OSEL_DRAW_DECK: return("You aren't carrying any decks from which to draw."); case OBJ_FOOD: return("You aren't carrying any food."); case OBJ_POTIONS: return("You aren't carrying any potions."); case OBJ_SCROLLS: case OBJ_BOOKS: return("You aren't carrying any books or scrolls."); case OBJ_WANDS: return("You aren't carrying any wands."); case OSEL_THROWABLE: return("You aren't carrying any items that might be thrown or fired."); case OSEL_BUTCHERY: return("You aren't carrying any sharp implements."); case OSEL_EVOKABLE: return("You aren't carrying any items that can be evoked."); case OSEL_FRUIT: return("You aren't carrying any fruit."); case OSEL_PONDER_ARM: return("You aren't carrying any armour which can be made ponderous."); } return("You aren't carrying any such object."); } void InvMenu::load_inv_items(int item_selector, int excluded_slot, MenuEntry *(*procfn)(MenuEntry *me)) { std::vector tobeshown; _get_inv_items_to_show(tobeshown, item_selector, excluded_slot); load_items(tobeshown, procfn); if (!item_count()) set_title(_no_selectables_message(item_selector)); else set_title(""); } #ifdef USE_TILE bool InvEntry::get_tiles(std::vector& tileset) const { if (!Options.tile_menu_icons) return (false); if (quantity <= 0) return (false); int idx = tileidx_item(*item); if (!idx) return (false); if (in_inventory(*item)) { const bool equipped = item_is_equipped(*item); if (equipped) { if (item_known_cursed(*item)) tileset.push_back(tile_def(TILE_ITEM_SLOT_EQUIP_CURSED, TEX_DEFAULT)); else tileset.push_back(tile_def(TILE_ITEM_SLOT_EQUIP, TEX_DEFAULT)); } else if (item_known_cursed(*item)) tileset.push_back(tile_def(TILE_ITEM_SLOT_CURSED, TEX_DEFAULT)); tileset.push_back(tile_def(TILE_ITEM_SLOT, TEX_DUNGEON)); tileset.push_back(tile_def(idx, TEX_DEFAULT)); // Is item melded? if (equipped && !you_tran_can_wear(*item)) tileset.push_back(tile_def(TILE_MESH, TEX_DEFAULT)); } else { // Do we want to display the floor type or is that too distracting? const coord_def c = item->pos; int ch = -1; if (c.x == 0) { // Store items. tileset.push_back(tile_def(TILE_ITEM_SLOT, TEX_DUNGEON)); } else if (c != coord_def()) { ch = tileidx_feature(grd(c), c.x, c.y); if (ch == TILE_FLOOR_NORMAL) ch = env.tile_flv(c).floor; else if (ch == TILE_WALL_NORMAL) ch = env.tile_flv(c).wall; tileset.push_back(tile_def(ch, TEX_DUNGEON)); } tileset.push_back(tile_def(idx, TEX_DEFAULT)); if (ch != -1) { // Needs to be displayed so as to not give away mimics in shallow water. if (ch == TILE_DNGN_SHALLOW_WATER) { tileset.push_back(tile_def(TILE_MASK_SHALLOW_WATER, TEX_DEFAULT)); } else if (ch == TILE_DNGN_SHALLOW_WATER_MURKY) { tileset.push_back(tile_def(TILE_MASK_SHALLOW_WATER_MURKY, TEX_DEFAULT)); } } } if (item->base_type == OBJ_WEAPONS || item->base_type == OBJ_MISSILES) { int brand = tile_known_weapon_brand(*item); if (brand) tileset.push_back(tile_def(brand, TEX_DEFAULT)); } else if (item->base_type == OBJ_CORPSES) { int brand = tile_corpse_brand(*item); if (brand) tileset.push_back(tile_def(brand, TEX_DEFAULT)); } return (true); } #endif bool InvMenu::is_selectable(int index) const { if (type == MT_DROP) { InvEntry *item = dynamic_cast(items[index]); if (item->is_item_cursed() && item->is_item_equipped()) return (false); std::string text = item->get_text(); if (text.find("!*") != std::string::npos || text.find("!d") != std::string::npos) { return (false); } } return Menu::is_selectable(index); } template int compare_item_str(const InvEntry *a, const InvEntry *b) { return (proc(a).compare(proc(b))); } template int compare_item(const InvEntry *a, const InvEntry *b) { return (int(proc(a)) - int(proc(b))); } // C++ needs anonymous subs already! std::string sort_item_basename(const InvEntry *a) { return a->get_basename(); } std::string sort_item_qualname(const InvEntry *a) { return a->get_qualname(); } std::string sort_item_fullname(const InvEntry *a) { return a->get_fullname(); } int sort_item_qty(const InvEntry *a) { return a->quantity; } int sort_item_slot(const InvEntry *a) { return a->item->link; } int sort_item_freshness(const InvEntry *a) { return a->item_freshness(); } bool sort_item_curse(const InvEntry *a) { return a->is_item_cursed(); } bool sort_item_glowing(const InvEntry *a) { return !a->is_item_glowing(); } bool sort_item_ego(const InvEntry *a) { return !a->is_item_ego(); } bool sort_item_art(const InvEntry *a) { return !a->is_item_art(); } bool sort_item_equipped(const InvEntry *a) { return !a->is_item_equipped(); } bool sort_item_identified(const InvEntry *a) { return !item_type_known(*(a->item)); } bool sort_item_charged(const InvEntry *a) { return (a->item->base_type != OBJ_WANDS || !item_is_evokable(*(a->item), true)); } static bool _compare_invmenu_items(const InvEntry *a, const InvEntry *b, const item_sort_comparators *cmps) { for (item_sort_comparators::const_iterator i = cmps->begin(); i != cmps->end(); ++i) { const int cmp = i->compare(a, b); if (cmp) return (cmp < 0); } return (false); } struct menu_entry_comparator { const menu_sort_condition *cond; menu_entry_comparator(const menu_sort_condition *c) : cond(c) { } bool operator () (const MenuEntry* a, const MenuEntry* b) const { const InvEntry *ia = dynamic_cast(a); const InvEntry *ib = dynamic_cast(b); return _compare_invmenu_items(ia, ib, &cond->cmp); } }; void init_item_sort_comparators(item_sort_comparators &list, const std::string &set) { static struct { const std::string cname; item_sort_fn cmp; } cmp_map[] = { { "basename", compare_item_str }, { "qualname", compare_item_str }, { "fullname", compare_item_str }, { "curse", compare_item }, { "glowing", compare_item }, { "ego", compare_item }, { "art", compare_item }, { "equipped", compare_item }, { "identified",compare_item }, { "charged", compare_item}, { "qty", compare_item }, { "slot", compare_item }, { "freshness", compare_item } }; list.clear(); std::vector cmps = split_string(",", set); for (int i = 0, size = cmps.size(); i < size; ++i) { std::string s = cmps[i]; if (s.empty()) continue; const bool negated = s[0] == '>'; if (s[0] == '<' || s[0] == '>') s = s.substr(1); for (unsigned ci = 0; ci < ARRAYSZ(cmp_map); ++ci) if (cmp_map[ci].cname == s) { list.push_back( item_comparator( cmp_map[ci].cmp, negated ) ); break; } } if (list.empty()) { list.push_back( item_comparator(compare_item_str)); } } const menu_sort_condition *InvMenu::find_menu_sort_condition() const { for (int i = Options.sort_menus.size() - 1; i >= 0; --i) if (Options.sort_menus[i].matches(type)) return &Options.sort_menus[i]; return (NULL); } void InvMenu::sort_menu(std::vector &invitems, const menu_sort_condition *cond) { if (!cond || cond->sort == -1 || (int) invitems.size() < cond->sort) return; std::sort( invitems.begin(), invitems.end(), menu_entry_comparator(cond) ); } void InvMenu::load_items(const std::vector &mitems, MenuEntry *(*procfn)(MenuEntry *me)) { FixedVector< int, NUM_OBJECT_CLASSES > inv_class(0); for (int i = 0, count = mitems.size(); i < count; ++i) inv_class[ mitems[i]->base_type ]++; menu_letter ckey; std::vector items_in_class; const menu_sort_condition *cond = find_menu_sort_condition(); for (int i = 0; i < NUM_OBJECT_CLASSES; ++i) { if (!inv_class[i]) continue; std::string subtitle = item_class_name(i); // Mention the class selection shortcuts. if (is_set(MF_MULTISELECT) && inv_class[i] > 1) { std::vector glyphs; _get_class_hotkeys(i, glyphs); if (!glyphs.empty()) { const std::string str = "Magical Staves and Rods"; // longest string subtitle += std::string(str.length() - subtitle.length() + 1, ' '); subtitle += "(select all with "; #ifdef USE_TILE // For some reason, this is only formatted correctly in the // Tiles version. subtitle += ""; #endif for (unsigned int k = 0; k < glyphs.size(); ++k) subtitle += glyphs[k]; #ifdef USE_TILE subtitle += ""; #endif subtitle += ")"; } } add_entry( new MenuEntry( subtitle, MEL_SUBTITLE ) ); items_in_class.clear(); for (int j = 0, count = mitems.size(); j < count; ++j) { if (mitems[j]->base_type != i) continue; items_in_class.push_back( new InvEntry(*mitems[j]) ); } sort_menu(items_in_class, cond); for (unsigned int j = 0; j < items_in_class.size(); ++j) { InvEntry *ie = items_in_class[j]; if (this->tag == "pickup") ie->tag = "pickup"; // If there's no hotkey, provide one. if (ie->hotkeys[0] == ' ') ie->hotkeys[0] = ckey++; do_preselect(ie); add_entry( procfn? (*procfn)(ie) : ie ); } } // Don't make a menu so tall that we recycle hotkeys on the same page. if (mitems.size() > 52 && (max_pagesize > 52 || max_pagesize == 0)) set_maxpagesize(52); } void InvMenu::do_preselect(InvEntry *ie) { if (!pre_select || pre_select->empty()) return; for (int i = 0, size = pre_select->size(); i < size; ++i) if (ie->item && ie->item == (*pre_select)[i].item) { ie->selected_qty = (*pre_select)[i].quantity; break; } } std::vector InvMenu::get_selitems() const { std::vector selected_items; for (int i = 0, count = sel.size(); i < count; ++i) { InvEntry *inv = dynamic_cast(sel[i]); selected_items.push_back( SelItem( inv->hotkeys[0], inv->selected_qty, inv->item ) ); } return (selected_items); } bool InvMenu::process_key( int key ) { if (items.size() && type == MT_DROP && (key == CONTROL('D') || key == '@')) { int newflag = is_set(MF_MULTISELECT) ? MF_SINGLESELECT | MF_ANYPRINTABLE : MF_MULTISELECT; flags &= ~(MF_SINGLESELECT | MF_MULTISELECT | MF_ANYPRINTABLE); flags |= newflag; deselect_all(); sel.clear(); draw_select_count(0, true); return (true); } return Menu::process_key( key ); } unsigned char InvMenu::getkey() const { unsigned char mkey = lastch; if (!isalnum(mkey) && mkey != '$' && mkey != '-' && mkey != '?' && mkey != '*' && mkey != ESCAPE && mkey != '\\') { mkey = ' '; } return (mkey); } ////////////////////////////////////////////////////////////////////////////// bool in_inventory(const item_def &i) { return i.pos.x == -1 && i.pos.y == -1; } unsigned char get_invent(int invent_type) { unsigned char select; while (true) { select = invent_select(NULL, MT_INVLIST, invent_type, -1, MF_SINGLESELECT); if (isalpha(select)) { const int invidx = letter_to_index(select); if (you.inv[invidx].is_valid()) describe_item( you.inv[invidx], true ); } else break; } if (!crawl_state.doing_prev_cmd_again) redraw_screen(); return select; } // end get_invent() 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 ("?"); case OBJ_SCROLLS: return ("scroll"); case OBJ_JEWELLERY: return ("jewellery"); case OBJ_POTIONS: return ("potion"); case OBJ_UNKNOWN_II: return ("?"); case OBJ_BOOKS: return ("book"); case OBJ_STAVES: return ("staff"); 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 (""); } std::vector select_items( const std::vector &items, const char *title, bool noselect, menu_type mtype ) { std::vector selected; if (!items.empty()) { InvMenu menu; menu.set_type(mtype); menu.set_title(title); if (mtype == MT_PICKUP) menu.set_tag("pickup"); menu.load_items(items); int new_flags = noselect ? MF_NOSELECT : MF_MULTISELECT | MF_ALLOW_FILTER; new_flags |= MF_SHOW_PAGENUMBERS; menu.set_flags(new_flags); menu.show(); selected = menu.get_selitems(); } 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 && itype != OBJ_FOOD && itype != OBJ_ARMOUR) { return (true); } switch (selector) { case OBJ_ARMOUR: return (itype == OBJ_ARMOUR && you_tran_can_wear(i)); case OSEL_FRUIT: return is_fruit(i); case OSEL_WORN_ARMOUR: return (itype == OBJ_ARMOUR && item_is_equipped(i)); case OSEL_UNIDENT: return !fully_identified(i); case OBJ_MISSILES: return (itype == OBJ_MISSILES || itype == OBJ_WEAPONS); case OSEL_THROWABLE: { if (i.base_type != OBJ_WEAPONS && i.base_type != OBJ_MISSILES) return (false); const launch_retval projected = is_launched(&you, you.weapon(), i); if (projected == LRET_FUMBLED) return (false); return (true); } case OBJ_WEAPONS: case OSEL_WIELD: return (itype == OBJ_WEAPONS || itype == OBJ_STAVES || (itype == OBJ_MISCELLANY && i.sub_type != MISC_RUNE_OF_ZOT)); case OSEL_BUTCHERY: return (itype == OBJ_WEAPONS && can_cut_meat(i)); case OBJ_SCROLLS: return (itype == OBJ_SCROLLS || itype == OBJ_BOOKS); case OSEL_RECHARGE: return (item_is_rechargeable(i, true)); case OSEL_EVOKABLE: return (item_is_evokable(i, true, true)); case OSEL_PONDER_ARM: if (itype != OBJ_ARMOUR || get_armour_slot(i) != EQ_BODY_ARMOUR) return (false); case OSEL_ENCH_ARM: return (is_enchantable_armour(i, true, true)); case OBJ_FOOD: return (itype == OBJ_FOOD && !is_inedible(i)); case OSEL_VAMP_EAT: return (itype == OBJ_CORPSES && i.sub_type == CORPSE_BODY && !food_is_rotten(i) && mons_has_blood(i.plus)); case OSEL_DRAW_DECK: return (is_deck(i)); case OSEL_EQUIP: { if (item_is_quivered(i)) return (true); for (int eq = 0; eq < NUM_EQUIP; eq++) if (you.equip[eq] == i.link) return (true); return (false); } 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 } static bool _is_item_selected(const item_def &i, int selector) { return (_item_class_selected(i, selector) || _userdef_item_selected(i, selector)); } static void _get_inv_items_to_show(std::vector &v, int selector, int excluded_slot) { for (int i = 0; i < ENDOFPACK; i++) { if (you.inv[i].is_valid() && you.inv[i].slot != excluded_slot && _is_item_selected(you.inv[i], selector)) { v.push_back( &you.inv[i] ); } } } static bool _any_items_to_select(int selector) { for (int i = 0; i < ENDOFPACK; i++) { if (you.inv[i].is_valid() && _is_item_selected(you.inv[i], selector)) { return (true); } } return (false); } unsigned char invent_select( const char *title, menu_type type, int item_selector, int excluded_slot, int flags, invtitle_annotator titlefn, std::vector *items, std::vector *filter, Menu::selitem_tfn selitemfn, const std::vector *pre_select ) { InvMenu menu(flags); menu.set_preselect(pre_select); menu.set_title_annotator(titlefn); menu.f_selitem = selitemfn; if (filter) menu.set_select_filter( *filter ); menu.load_inv_items(item_selector, excluded_slot); menu.set_type(type); // Don't override title if there are no items. if (title && menu.item_count()) menu.set_title(title); menu.show(true); if (items) *items = menu.get_selitems(); return (menu.getkey()); } void browse_inventory( bool show_price ) { InvShowPrices show_item_prices(show_price); get_invent(OSEL_ANY); } // Reads in digits for a count and apprends then to val, the // return value is the character that stopped the reading. static unsigned char _get_invent_quant( unsigned char keyin, int &quant ) { quant = keyin - '0'; while (true) { keyin = get_ch(); if (!isdigit( keyin )) break; quant *= 10; quant += (keyin - '0'); if (quant > 9999999) { quant = 9999999; keyin = '\0'; break; } } 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, as it is by 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 prompt_invent_items( const char *prompt, menu_type mtype, int type_expect, invtitle_annotator titlefn, bool allow_auto_list, bool allow_easy_quit, const char other_valid_char, std::vector *select_filter, Menu::selitem_tfn fn, const std::vector *pre_select ) { unsigned char keyin = 0; int ret = PROMPT_ABORT; bool need_redraw = false; bool need_prompt = true; bool need_getch = true; bool auto_list = Options.auto_list && allow_auto_list; if (auto_list) { need_prompt = need_getch = false; keyin = '?'; } std::vector items; int count = -1; while (true) { if (need_redraw && !crawl_state.doing_prev_cmd_again) { 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 && (!pre_select || pre_select->empty()) ? MF_SINGLESELECT | MF_EASY_EXIT | MF_ANYPRINTABLE : MF_MULTISELECT | MF_ALLOW_FILTER; // The "view inventory listing" mode. int ch = invent_select( prompt, mtype, keyin == '*' ? OSEL_ANY : type_expect, -1, selmode, titlefn, &items, select_filter, fn, pre_select ); if ((selmode & MF_SINGLESELECT) || ch == ESCAPE) { keyin = ch; need_getch = false; } else { keyin = 0; need_getch = true; } if (items.size()) { if (!crawl_state.doing_prev_cmd_again) { redraw_screen(); mesclr(true); } for (unsigned int i = 0; i < items.size(); ++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 (!you.inv[ret].is_valid()) mpr("You don't have any such object."); else break; } else if (!isspace( keyin )) { // We've got a character we don't understand... canned_msg( MSG_HUH ); } else { // We're going to loop back up, so don't draw another prompt. need_prompt = false; } } if (ret != PROMPT_ABORT) { items.push_back( SelItem( ret, count, ret != PROMPT_GOT_SPECIAL ? &you.inv[ret] : NULL ) ); } return items; } static int _digit_to_index( char digit, operation_types oper ) { const char iletter = static_cast(oper); for ( int i = 0; i < ENDOFPACK; ++i ) { if (you.inv[i].is_valid()) { const std::string& r(you.inv[i].inscription); // Note that r.size() is unsigned. for (unsigned int j = 0; j + 2 < r.size(); ++j) { if (r[j] == '@' && (r[j+1] == iletter || r[j+1] == '*') && r[j+2] == digit ) { return i; } } } } return -1; } bool has_warning_inscription(const item_def& item, operation_types oper) { const char iletter = static_cast(oper); const std::string& r(item.inscription); for (unsigned int i = 0; i + 1 < r.size(); ++i) { if (r[i] == '!') { if (r[i+1] == iletter || r[i+1] == '*') return (true); else if (oper == OPER_ZAP && r[i+1] == 'z') // for the 0.3.4. keys return (true); else if (oper == OPER_EVOKE && (r[i+1] == 'V' || tolower(r[i+1]) == 'z')) { return (true); } } } return (false); } // Checks if current item (to be removed) has a warning inscription // and prompts the user for confirmation. bool check_old_item_warning( const item_def& item, operation_types oper ) { item_def old_item; std::string prompt; if (oper == OPER_WIELD) // can we safely unwield old item? { if (!you.weapon()) return (true); old_item = *you.weapon(); if (!has_warning_inscription(old_item, OPER_WIELD)) return (true); prompt += "Really unwield "; } else if (oper == OPER_WEAR) // can we safely take off old item? { if (item.base_type != OBJ_ARMOUR) return (true); equipment_type eq_slot = get_armour_slot(item); if (you.equip[eq_slot] == -1) return (true); old_item = you.inv[you.equip[eq_slot]]; if (!has_warning_inscription(old_item, OPER_TAKEOFF)) return (true); prompt += "Really take off "; } else if (oper == OPER_PUTON) // can we safely remove old item? { if (item.base_type != OBJ_JEWELLERY) return (true); if (jewellery_is_amulet(item)) { if (you.equip[EQ_AMULET] == -1) return (true); old_item = you.inv[you.equip[EQ_AMULET]]; if (!has_warning_inscription(old_item, OPER_TAKEOFF)) return (true); prompt += "Really remove "; } else // rings handled in prompt_ring_to_remove return (true); } else // anything else doesn't have a counterpart return (true); // now ask prompt += old_item.name(DESC_INVENTORY); prompt += '?'; return yesno(prompt.c_str(), false, 'n'); } static std::string _operation_verb(operation_types oper) { switch (oper) { case OPER_WIELD: return "wield"; case OPER_QUAFF: return "quaff"; case OPER_DROP: return "drop"; case OPER_EAT: return (you.species == SP_VAMPIRE ? "drain" : "eat"); case OPER_TAKEOFF: return "take off"; case OPER_WEAR: return "wear"; case OPER_PUTON: return "put on"; case OPER_REMOVE: return "remove"; case OPER_READ: return "read"; case OPER_MEMORISE: return "memorise from"; case OPER_ZAP: return "zap"; case OPER_EXAMINE: return "examine"; case OPER_FIRE: return "fire"; case OPER_PRAY: return "sacrifice"; case OPER_EVOKE: return "evoke"; case OPER_DESTROY: return "destroy"; case OPER_QUIVER: return "quiver"; case OPER_ANY: default: return "choose"; } } bool _removing_amulet_of_faith(const item_def &item, operation_types oper) { return (oper == OPER_REMOVE && item.base_type == OBJ_JEWELLERY && item.sub_type == AMU_FAITH); } // Returns true if user OK'd it (or no warning), false otherwise. bool check_warning_inscriptions( const item_def& item, operation_types oper ) { if (item.is_valid() && (has_warning_inscription(item, oper) || (_removing_amulet_of_faith(item, oper) && you.religion != GOD_NO_GOD))) { // When it's about destroying an item, don't even ask. // If the player really wants to do that, they'll have // to remove the inscription. if (oper == OPER_DESTROY) return (false); if (oper == OPER_WEAR) { if (item.base_type != OBJ_ARMOUR) return (true); // Don't ask if item already worn. int equip = you.equip[get_armour_slot(item)]; if (equip != -1 && item.link == equip) return (check_old_item_warning(item, oper)); } else if (oper == OPER_PUTON) { if (item.base_type != OBJ_JEWELLERY) return (true); // Don't ask if item already worn. int equip = -1; if (jewellery_is_amulet(item)) equip = you.equip[EQ_AMULET]; else { equip = you.equip[EQ_LEFT_RING]; if (equip != -1 && item.link == equip) return (check_old_item_warning(item, oper)); // Or maybe the other ring? equip = you.equip[EQ_RIGHT_RING]; } if (equip != -1 && item.link == equip) return (check_old_item_warning(item, oper)); } std::string prompt = "Really " + _operation_verb(oper) + " "; prompt += (in_inventory(item) ? item.name(DESC_INVENTORY) : item.name(DESC_NOCAP_A)); prompt += "?"; return (yesno(prompt.c_str(), false, 'n') && check_old_item_warning(item, oper)); } else return (check_old_item_warning(item, oper)); } // 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". // It returns PROMPT_NOTHING if there are no matching items. // // Note: This function never checks if the item is appropriate. int prompt_invent_item( const char *prompt, menu_type mtype, int type_expect, bool must_exist, bool allow_auto_list, bool allow_easy_quit, const char other_valid_char, int excluded_slot, int *const count, operation_types oper, bool allow_list_known ) { if (!_any_items_to_select(type_expect) && type_expect == OSEL_THROWABLE && oper == OPER_FIRE && mtype == MT_INVLIST) { type_expect = OSEL_ANY; } if (!_any_items_to_select(type_expect) && type_expect != OSEL_WIELD && type_expect != OSEL_BUTCHERY && mtype == MT_INVLIST) { mprf(MSGCH_PROMPT, "%s", _no_selectables_message(type_expect).c_str()); return (PROMPT_NOTHING); } unsigned char keyin = 0; int ret = PROMPT_ABORT; bool need_redraw = false; bool need_prompt = true; bool need_getch = true; bool auto_list = Options.auto_list && allow_auto_list; if (auto_list) { need_prompt = need_getch = false; if (_any_items_to_select(type_expect)) keyin = '?'; else keyin = '*'; } while (true) { if (need_redraw && !crawl_state.doing_prev_cmd_again) { redraw_screen(); mesclr( true ); } if (need_prompt) mpr(prompt, MSGCH_PROMPT); else flush_prev_message(); 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 == '*') { // The "view inventory listing" mode. std::vector< SelItem > items; keyin = invent_select( prompt, mtype, keyin == '*' ? OSEL_ANY : type_expect, excluded_slot, MF_SINGLESELECT | MF_ANYPRINTABLE | MF_NO_SELECT_QTY | MF_EASY_EXIT, NULL, &items ); if (allow_list_known && keyin == '\\') { if (check_item_knowledge(true)) keyin = '?'; else mpr("You don't recognise anything yet!"); } need_getch = false; // Don't redraw if we're just going to display another listing need_redraw = (keyin != '?' && keyin != '*') && !(count && auto_list && isdigit(keyin)); need_prompt = need_redraw; if (items.size()) { if (count) *count = items[0].quantity; if (!crawl_state.doing_prev_cmd_again) { redraw_screen(); mesclr( true ); } } } else if (count != NULL && isdigit( keyin )) { // The "read in quantity" mode keyin = _get_invent_quant( keyin, *count ); need_prompt = false; need_getch = false; if (auto_list) need_redraw = true; } else if (count == NULL && isdigit( keyin )) { // scan for our item int res = _digit_to_index( keyin, oper ); if (res != -1) { ret = res; if (check_warning_inscriptions( you.inv[ret], oper )) break; } } else if (keyin == ESCAPE || (Options.easy_quit_item_prompts && allow_easy_quit && keyin == ' ')) { ret = PROMPT_ABORT; break; } else if (allow_list_known && keyin == '\\') { if (check_item_knowledge(true)) { keyin = '?'; need_getch = false; } else mpr("You don't recognise anything yet!"); } else if (isalpha( keyin )) { ret = letter_to_index( keyin ); if (must_exist && !you.inv[ret].is_valid()) mpr("You don't have any such object."); else if (check_warning_inscriptions( you.inv[ret], oper )) break; } else if (!isspace( keyin )) { // We've got a character we don't understand... canned_msg( MSG_HUH ); } else { // We're going to loop back up, so don't draw another prompt. need_prompt = false; } } return (ret); } bool prompt_failed(int retval, std::string msg) { if (retval != PROMPT_ABORT && retval != PROMPT_NOTHING) return (false); if (msg.empty()) { if (retval == PROMPT_ABORT) canned_msg(MSG_OK); } else mprf(MSGCH_PROMPT, msg.c_str()); crawl_state.cancel_cmd_repeat(); return (true); } bool item_is_evokable(const item_def &item, bool known, bool all_wands, bool msg) { if (is_unrandom_artefact(item)) { const unrandart_entry* entry = get_unrand_entry(item.special); if (entry->evoke_func && item_type_known(item)) { if (item_is_equipped(item)) return (true); if (msg) mpr("That item can only be evoked when wielded."); return (false); } // Unrandart might still be evokable (e.g., reaching) } const bool wielded = (you.equip[EQ_WEAPON] == item.link); switch (item.base_type) { case OBJ_WANDS: if (all_wands) return (true); if (item.plus2 == ZAPCOUNT_EMPTY) { if (msg) mpr("This wand has no charges."); return (false); } return (true); case OBJ_WEAPONS: if (!wielded && !msg) return (false); if (get_weapon_brand(item) == SPWPN_REACHING && item_type_known(item)) { if (!wielded) { if (msg) mpr("That item can only be evoked when wielded."); return (false); } return (true); } if (msg) mpr("That item cannot be evoked!"); return (false); case OBJ_STAVES: if (item_is_rod(item) || !known && !item_type_known(item) || item.sub_type == STAFF_CHANNELING && item_type_known(item)) { if (!wielded) { if (msg) mpr("That item can only be evoked when wielded."); return (false); } return (true); } if (msg) mpr("That item cannot be evoked!"); return (false); case OBJ_MISCELLANY: if (is_deck(item)) { if (!wielded) { if (msg) mpr("That item can only be evoked when wielded."); return (false); } return (true); } if (item.sub_type != MISC_LANTERN_OF_SHADOWS && item.sub_type != MISC_EMPTY_EBONY_CASKET && item.sub_type != MISC_RUNE_OF_ZOT) { return (true); } // else fall through default: if (msg) mpr("That item cannot be evoked!"); return (false); } }