/* * File: invent.cc * Summary: Functions for inventory related commands. * Written by: Linley Henzell * * Modified for Crawl Reference by $Author$ on $Date$ * * Change History (most recent first): * * <5> 10/9/99 BCR Added wizard help screen * <4> 10/1/99 BCR Clarified help screen * <3> 6/9/99 DML Autopickup * <2> 5/20/99 BWR Extended screen lines support * <1> -/--/-- LRH Created */ #include "AppHdr.h" #include "invent.h" #include #include #include #ifdef DOS #include #endif #include "externs.h" #include "clua.h" #include "itemname.h" #include "items.h" #include "macro.h" #include "player.h" #include "shopping.h" #include "stuff.h" #include "view.h" #include "menu.h" struct InvTitle : public MenuEntry { 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; } std::string get_text() const { return titlefn? titlefn( m->get_flags(), MenuEntry::get_text() ) : MenuEntry::get_text(); } }; class InvShowPrices; class InvEntry : public MenuEntry { private: static bool show_prices; static char temp_id[4][50]; static void set_show_prices(bool doshow); friend class InvShowPrices; public: const item_def *item; InvEntry( const item_def &i ) : MenuEntry( "", MEL_ITEM ), item( &i ) { data = const_cast( 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; 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); quantity = i.quantity; } std::string get_text() const { char buf[ITEMNAME_SIZE]; char suffix[ITEMNAME_SIZE] = ""; 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) { switch (i.base_type) { 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; } } }; bool InvEntry::show_prices = false; char InvEntry::temp_id[4][50]; void InvEntry::set_show_prices(bool doshow) { if ((show_prices = doshow)) { memset(temp_id, 1, sizeof temp_id); } } class InvShowPrices { public: InvShowPrices(bool doshow = true) { InvEntry::set_show_prices(doshow); } ~InvShowPrices() { InvEntry::set_show_prices(false); } }; class InvMenu : public Menu { public: InvMenu(int mflags = MF_MULTISELECT) : Menu(mflags) { } protected: bool process_key(int key); }; 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 ); } bool in_inventory( const item_def &i ) { return i.x == -1 && i.y == -1; } unsigned char get_invent( int invent_type ) { unsigned char nothing = invent_select( invent_type ); redraw_screen(); return (nothing); } // 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 ("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 (""); } class MenuEntryPtrComparer { public: bool operator() (const MenuEntry* a, const MenuEntry* b ) const { return *a < *b; } }; void populate_item_menu( Menu *menu, const std::vector &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; std::vector items_in_class; for (int i = 0; i < NUM_OBJECT_CLASSES; ++i) { if (!inv_class[i]) continue; menu->add_entry( new MenuEntry( item_class_name(i), MEL_SUBTITLE ) ); items_in_class.clear(); for (int j = 0, count = items.size(); j < count; ++j) { if (items[j].base_type != i) continue; items_in_class.push_back( new InvEntry(items[j]) ); } std::sort( items_in_class.begin(), items_in_class.end(), MenuEntryPtrComparer() ); for (unsigned int j = 0; j < items_in_class.size(); ++j) { InvEntry *ie = items_in_class[j]; ie->hotkeys[0] = ckey++; callback(ie); menu->add_entry( ie ); } } } std::vector select_items( std::vector &items, const char *title, bool noselect ) { std::vector selected; 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 ]++; Menu menu; menu.set_title( new MenuEntry(title) ); char ckey = 'a'; std::vector items_in_class; for (int i = 0; i < NUM_OBJECT_CLASSES; ++i) { if (!inv_class[i]) continue; menu.add_entry( new MenuEntry( item_class_name(i), MEL_SUBTITLE ) ); items_in_class.clear(); for (int j = 0, count = items.size(); j < count; ++j) { if (items[j]->base_type != i) continue; items_in_class.push_back( new InvEntry( *items[j]) ); } /* sort the items inside a class */ std::sort( items_in_class.begin(), items_in_class.end(), MenuEntryPtrComparer() ); for (unsigned int j = 0; j < items_in_class.size(); ++j) { InvEntry *ie = items_in_class[j]; ie->hotkeys[0] = ckey; menu.add_entry( ie ); ckey = ckey == 'z'? 'A' : ckey == 'Z'? 'a' : ckey + 1; } } menu.set_flags( noselect ? MF_NOSELECT : (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 } 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) { 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] ); } } static void set_vectitem_classes(const std::vector &v, FixedVector &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 ]++; } unsigned char invent_select( int item_selector, int flags, std::string (*titlefn)( int menuflags, const std::string &oldt ), std::vector *items, std::vector *filter, Menu::selitem_tfn selitemfn ) { InvMenu menu; menu.f_selitem = selitemfn; if (filter) menu.set_select_filter( *filter ); FixedVector< int, NUM_OBJECT_CLASSES > inv_class2; for (int i = 0; i < NUM_OBJECT_CLASSES; i++) inv_class2[i] = 0; int inv_count = 0; for (int i = 0; i < ENDOFPACK; i++) { if (you.inv[i].quantity) { inv_class2[ you.inv[i].base_type ]++; inv_count++; } } if (!inv_count) { menu.set_title( new MenuEntry( "You aren't carrying anything." ) ); menu.show(); return 0; } std::vector tobeshown; get_inv_items_to_show( tobeshown, item_selector ); set_vectitem_classes( tobeshown, inv_class2 ); if (tobeshown.size()) { const int cap = carrying_capacity(); 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 ) ); 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 ) ); 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 } // end of i loop. } else { std::string s; if (item_selector == -1) s = "You aren't carrying anything."; else { 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 s = "You aren't carrying any such object."; } menu.set_title( new MenuEntry( s ) ); } menu.set_flags( flags ); std::vector< MenuEntry * > sel = menu.show(); if (items) { for (int i = 0, count = sel.size(); i < count; ++i) items->push_back( SelItem( sel[i]->hotkeys[0], sel[i]->selected_qty ) ); } unsigned char mkey = menu.getkey(); if (!isalnum(mkey) && mkey != '$' && mkey != '-' && mkey != '?' && mkey != '*') mkey = ' '; return mkey; } unsigned char invent( int item_class_inv, bool show_price ) { InvShowPrices show_item_prices(show_price); return (invent_select(item_class_inv)); } // end invent() // 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'; for(;;) { 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 (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 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 *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 (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 (!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; } static int digit_to_index( char digit, operation_types oper ) { int i; unsigned int j; char iletter = (char)(oper); for ( i = 0; i < ENDOFPACK; ++i ) { if (is_valid_item(you.inv[i])) { const std::string& r(you.inv[i].inscription); /* note that r.size() is unsigned */ for ( 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; } /* return true if user OK'd it (or no warning), false otherwise */ static bool check_warning_inscriptions( const item_def& item, operation_types oper ) { unsigned int i; char iletter = (char)(oper); char name[ITEMNAME_SIZE]; char prompt[ITEMNAME_SIZE + 100]; item_name(item, DESC_INVENTORY, name, false); strcpy( prompt, "Really choose "); strncat( prompt, name, ITEMNAME_SIZE ); strcat( prompt, "?"); const std::string& r(item.inscription); for ( i = 0; i + 1 < r.size(); ++i ) { if ( r[i] == '!' && (r[i+1] == iletter || r[i+1] == '*') ) { return yesno(prompt, false, 'n'); } } return true; } // 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. int prompt_invent_item( const char *prompt, int type_expect, bool must_exist, bool allow_auto_list, bool allow_easy_quit, const char other_valid_char, int *const count, operation_types oper ) { 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) { std::vector< SelItem > items; // pretend the player has hit '?' and setup state. 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; // Don't redraw if we're just going to display another listing need_redraw = (keyin != '?' && keyin != '*'); // A prompt is nice for when we're moving to "count" mode. need_prompt = (count != NULL && isdigit( keyin )); } 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 == '*') { // The "view inventory listing" mode. 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; // Don't redraw if we're just going to display another listing need_redraw = (keyin != '?' && keyin != '*'); // A prompt is nice for when we're moving to "count" mode. need_prompt = (count != NULL && isdigit( keyin )); } else if (count != NULL && isdigit( keyin )) { // The "read in quantity" mode keyin = get_invent_quant( keyin, *count ); need_prompt = false; need_getch = false; } /*** HP CHANGE ***/ 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 (isalpha( keyin )) { ret = letter_to_index( keyin ); if (must_exist && !is_valid_item( you.inv[ret] )) mpr( "You do not 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 ); } } return (ret); }