/* * File: items.cc * Summary: Misc (mostly) inventory related functions. * Written by: Linley Henzell */ #include "AppHdr.h" #include "items.h" #include "cio.h" #include "clua.h" #include #include #include #include #include "externs.h" #include "arena.h" #include "artefact.h" #include "beam.h" #include "branch.h" #include "coord.h" #include "coordit.h" #include "dbg-util.h" #include "debug.h" #include "decks.h" #include "delay.h" #include "dgnevent.h" #include "directn.h" #include "effects.h" #include "env.h" #include "food.h" #include "hiscores.h" #include "invent.h" #include "it_use2.h" #include "item_use.h" #include "itemname.h" #include "itemprop.h" #include "libutil.h" #include "message.h" #include "misc.h" #include "mon-util.h" #include "mutation.h" #include "notes.h" #include "options.h" #include "place.h" #include "player.h" #include "quiver.h" #include "religion.h" #include "shopping.h" #include "showsymb.h" #include "skills2.h" #include "spl-book.h" #include "spl-util.h" #include "state.h" #include "stuff.h" #include "areas.h" #include "stash.h" #include "state.h" #include "terrain.h" #include "travel.h" #include "tutorial.h" #include "viewchar.h" #include "viewgeom.h" #include "xom.h" #define PORTAL_VAULT_ORIGIN_KEY "portal_vault_origin" static bool _invisible_to_player( const item_def& item ); static void _autoinscribe_item( item_def& item ); static void _autoinscribe_floor_items(); static void _autoinscribe_inventory(); static inline std::string _autopickup_item_name(const item_def &item); static bool will_autopickup = false; static bool will_autoinscribe = false; // Used to be called "unlink_items", but all it really does is make // sure item coordinates are correct to the stack they're in. -- bwr void fix_item_coordinates() { // Nails all items to the ground (i.e. sets x,y). for (int x = 0; x < GXM; x++) for (int y = 0; y < GYM; y++) { int i = igrd[x][y]; while (i != NON_ITEM) { mitm[i].pos.x = x; mitm[i].pos.y = y; i = mitm[i].link; } } } // This function uses the items coordinates to relink all the igrd lists. void link_items(void) { // First, initialise igrd array. igrd.init(NON_ITEM); // Link all items on the grid, plus shop inventory, // but DON'T link the huge pile of monster items at (-2,-2). for (int i = 0; i < MAX_ITEMS; i++) { // Don't mess with monster held items, since the index of the holding // monster is stored in the link field. if (mitm[i].held_by_monster()) continue; if (!mitm[i].is_valid()) { // Item is not assigned. Ignore. mitm[i].link = NON_ITEM; continue; } // link to top mitm[i].link = igrd( mitm[i].pos ); igrd( mitm[i].pos ) = i; } } static bool _item_ok_to_clean(int item) { // Never clean food or Orbs. if (mitm[item].base_type == OBJ_FOOD || mitm[item].base_type == OBJ_ORBS) return (false); // Never clean runes. if (mitm[item].base_type == OBJ_MISCELLANY && mitm[item].sub_type == MISC_RUNE_OF_ZOT) { return (false); } return (true); } // Returns index number of first available space, or NON_ITEM for // unsuccessful cleanup (should be exceedingly rare!) static int _cull_items(void) { crawl_state.cancel_cmd_repeat(); // XXX: Not the prettiest of messages, but the player // deserves to know whenever this kicks in. -- bwr mpr("Too many items on level, removing some.", MSGCH_WARN); // Rules: // 1. Don't cleanup anything nearby the player // 2. Don't cleanup shops // 3. Don't cleanup monster inventory // 4. Clean 15% of items // 5. never remove food, orbs, runes // 7. uniques weapons are moved to the abyss // 8. randarts are simply lost // 9. unrandarts are 'destroyed', but may be generated again int first_cleaned = NON_ITEM; // 2. Avoid shops by avoiding (0,5..9). // 3. Avoid monster inventory by iterating over the dungeon grid. for (rectangle_iterator ri(1); ri; ++ri) { if (grid_distance( you.pos(), *ri ) <= 9) continue; for (stack_iterator si(*ri); si; ++si) { if (_item_ok_to_clean(si->index()) && x_chance_in_y(15, 100)) { if (is_unrandom_artefact(*si)) { // 7. Move uniques to abyss. set_unique_item_status(*si, UNIQ_LOST_IN_ABYSS); } if (first_cleaned == NON_ITEM) first_cleaned = si->index(); // POOF! destroy_item( si->index() ); } } } return (first_cleaned); } // Reduce quantity of an inventory item, do cleanup if item goes away. // // Returns true if stack of items no longer exists. bool dec_inv_item_quantity( int obj, int amount, bool suppress_burden ) { bool ret = false; if (you.equip[EQ_WEAPON] == obj) you.wield_change = true; you.m_quiver->on_inv_quantity_changed(obj, amount); if (you.inv[obj].quantity <= amount) { for (int i = 0; i < NUM_EQUIP; i++) { if (you.equip[i] == obj) { if (i == EQ_WEAPON) { unwield_item(); canned_msg( MSG_EMPTY_HANDED ); } you.equip[i] = -1; } } you.inv[obj].base_type = OBJ_UNASSIGNED; you.inv[obj].quantity = 0; you.inv[obj].props.clear(); ret = true; // If we're repeating a command, the repetitions used up the // item stack being repeated on, so stop rather than move onto // the next stack. crawl_state.cancel_cmd_repeat(); crawl_state.cancel_cmd_again(); } else { you.inv[obj].quantity -= amount; } if (!suppress_burden) burden_change(); return (ret); } // Reduce quantity of a monster/grid item, do cleanup if item goes away. // // Returns true if stack of items no longer exists. bool dec_mitm_item_quantity( int obj, int amount ) { if (mitm[obj].quantity <= amount) amount = mitm[obj].quantity; if (mitm[obj].quantity == amount) { destroy_item( obj ); // If we're repeating a command, the repetitions used up the // item stack being repeated on, so stop rather than move onto // the next stack. crawl_state.cancel_cmd_repeat(); crawl_state.cancel_cmd_again(); return (true); } mitm[obj].quantity -= amount; return (false); } void inc_inv_item_quantity( int obj, int amount, bool suppress_burden ) { if (you.equip[EQ_WEAPON] == obj) you.wield_change = true; you.m_quiver->on_inv_quantity_changed(obj, amount); you.inv[obj].quantity += amount; if (!suppress_burden) burden_change(); } void inc_mitm_item_quantity( int obj, int amount ) { mitm[obj].quantity += amount; } void init_item( int item ) { if (item == NON_ITEM) return; mitm[item].clear(); } // Returns an unused mitm slot, or NON_ITEM if none available. // The reserve is the number of item slots to not check. // Items may be culled if a reserve <= 10 is specified. int get_item_slot( int reserve ) { ASSERT( reserve >= 0 ); if (crawl_state.arena) reserve = 0; int item = NON_ITEM; for (item = 0; item < (MAX_ITEMS - reserve); item++) if (!mitm[item].is_valid()) break; if (item >= MAX_ITEMS - reserve) { if (crawl_state.arena) { item = arena_cull_items(); // If arena_cull_items() can't free up any space then // _cull_items() won't be able to either, so give up. if (item == NON_ITEM) return (NON_ITEM); } else item = (reserve <= 10) ? _cull_items() : NON_ITEM; if (item == NON_ITEM) return (NON_ITEM); } ASSERT( item != NON_ITEM ); init_item( item ); return (item); } void unlink_item( int dest ) { // Don't destroy non-items, may be called after an item has been // reduced to zero quantity however. if (dest == NON_ITEM || !mitm[dest].is_valid()) return; monsters* monster = mitm[dest].holding_monster(); if (monster != NULL) { for (int i = 0; i < NUM_MONSTER_SLOTS; i++) { if (monster->inv[i] == dest) { monster->inv[i] = NON_ITEM; mitm[dest].pos.reset(); mitm[dest].link = NON_ITEM; return; } } mprf(MSGCH_ERROR, "Item %s claims to be held by monster %s, but " "it isn't in the monster's inventory.", mitm[dest].name(DESC_PLAIN, false, true).c_str(), monster->name(DESC_PLAIN, true).c_str()); // Don't return so the debugging code can take a look at it. } // Unlinking a newly created item, or a a temporary one, or an item in // the player's inventory. else if (mitm[dest].pos.origin() || mitm[dest].pos.equals(-1, -1)) { mitm[dest].pos.reset(); mitm[dest].link = NON_ITEM; return; } else { ASSERT(in_bounds(mitm[dest].pos) || is_shop_item(mitm[dest])); // Linked item on map: // // Use the items (x,y) to access the list (igrd[x][y]) where // the item should be linked. // First check the top: if (igrd(mitm[dest].pos) == dest) { // link igrd to the second item igrd(mitm[dest].pos) = mitm[dest].link; mitm[dest].pos.reset(); mitm[dest].link = NON_ITEM; return; } // Okay, item is buried, find item that's on top of it. for (stack_iterator si(mitm[dest].pos); si; ++si) { // Find item linking to dest item. if (si->is_valid() && si->link == dest) { // unlink dest si->link = mitm[dest].link; mitm[dest].pos.reset(); mitm[dest].link = NON_ITEM; return; } } } #ifdef DEBUG // Okay, the sane ways are gone... let's warn the player: mprf(MSGCH_ERROR, "BUG WARNING: Problems unlinking item '%s', (%d, %d)!!!", mitm[dest].name(DESC_PLAIN).c_str(), mitm[dest].pos.x, mitm[dest].pos.y ); // Okay, first we scan all items to see if we have something // linked to this item. We're not going to return if we find // such a case... instead, since things are already out of // alignment, let's assume there might be multiple links as well. bool linked = false; int old_link = mitm[dest].link; // used to try linking the first // Clean the relevant parts of the object. mitm[dest].base_type = OBJ_UNASSIGNED; mitm[dest].quantity = 0; mitm[dest].link = NON_ITEM; mitm[dest].pos.reset(); mitm[dest].props.clear(); // Look through all items for links to this item. for (int c = 0; c < MAX_ITEMS; c++) { if (mitm[c].is_valid() && mitm[c].link == dest) { // unlink item mitm[c].link = old_link; if (!linked) { old_link = NON_ITEM; linked = true; } } } // Now check the grids to see if it's linked as a list top. for (int c = 2; c < (GXM - 1); c++) for (int cy = 2; cy < (GYM - 1); cy++) { if (igrd[c][cy] == dest) { igrd[c][cy] = old_link; if (!linked) { old_link = NON_ITEM; // cleaned after the first linked = true; } } } // Okay, finally warn player if we didn't do anything. if (!linked) mpr("BUG WARNING: Item didn't seem to be linked at all.", MSGCH_ERROR); #endif } void destroy_item( item_def &item, bool never_created ) { if (!item.is_valid()) return; if (never_created) { if (is_unrandom_artefact(item)) set_unique_item_status(item, UNIQ_NOT_EXISTS); } item.clear(); } void destroy_item( int dest, bool never_created ) { // Don't destroy non-items, but this function may be called upon // to remove items reduced to zero quantity, so we allow "invalid" // objects in. if (dest == NON_ITEM || !mitm[dest].is_valid()) return; unlink_item( dest ); destroy_item( mitm[dest], never_created ); } static void _handle_gone_item(const item_def &item) { if (you.level_type == LEVEL_ABYSS && place_type(item.orig_place) == LEVEL_ABYSS && !(item.flags & ISFLAG_BEEN_IN_INV)) { if (is_unrandom_artefact(item)) set_unique_item_status(item, UNIQ_LOST_IN_ABYSS); } if (is_rune(item)) { if ((item.flags & ISFLAG_BEEN_IN_INV)) { if (is_unique_rune(item)) you.attribute[ATTR_UNIQUE_RUNES] -= item.quantity; else if (item.plus == RUNE_ABYSSAL) you.attribute[ATTR_ABYSSAL_RUNES] -= item.quantity; else you.attribute[ATTR_DEMONIC_RUNES] -= item.quantity; } } } void item_was_lost(const item_def &item) { _handle_gone_item( item ); xom_check_lost_item( item ); } void item_was_destroyed(const item_def &item, int cause) { _handle_gone_item( item ); xom_check_destroyed_item( item, cause ); } void lose_item_stack( const coord_def& where ) { for (stack_iterator si(where); si; ++si) { if (si ->is_valid()) // FIXME is this check necessary? { item_was_lost(*si); si->clear(); } } igrd(where) = NON_ITEM; } void destroy_item_stack( int x, int y, int cause ) { for (stack_iterator si(coord_def(x,y)); si; ++si) { if (si ->is_valid()) // FIXME is this check necessary? { item_was_destroyed( *si, cause); si->clear(); } } igrd[x][y] = NON_ITEM; } static bool _invisible_to_player( const item_def& item ) { return strstr(item.inscription.c_str(), "=k") != 0; } static int _count_nonsquelched_items( int obj ) { int result = 0; for (stack_iterator si(obj); si; ++si) if (!_invisible_to_player(*si)) ++result; return result; } // Fill items with the items on a square. // Squelched items (marked with =k) are ignored, unless // the square contains *only* squelched items, in which case they // are included. If force_squelch is true, squelched items are // never displayed. void item_list_on_square( std::vector& items, int obj, bool force_squelch ) { const bool have_nonsquelched = (force_squelch || _count_nonsquelched_items(obj)); // Loop through the items. for (stack_iterator si(obj); si; ++si) { // Add them to the items list if they qualify. if (!have_nonsquelched || !_invisible_to_player(*si)) items.push_back( & (*si) ); } } bool need_to_autopickup() { return will_autopickup; } void request_autopickup(bool do_pickup) { will_autopickup = do_pickup; } // 2 - artefact, 1 - glowing/runed, 0 - mundane int item_name_specialness(const item_def& item) { // All jewellery is worth looking at. // And we can always tell from the name if it's an artefact. if (item.base_type == OBJ_JEWELLERY ) return ( is_artefact(item) ? 2 : 1 ); if (item.base_type != OBJ_WEAPONS && item.base_type != OBJ_ARMOUR && item.base_type != OBJ_MISSILES) { return 0; } if (item_type_known(item)) { if (is_artefact(item)) return 2; // XXX Unite with l_item_branded() in clua.cc bool branded = false; switch (item.base_type) { case OBJ_WEAPONS: branded = get_weapon_brand(item) != SPWPN_NORMAL; break; case OBJ_ARMOUR: branded = get_armour_ego_type(item) != SPARM_NORMAL; break; case OBJ_MISSILES: branded = get_ammo_brand(item) != SPMSL_NORMAL; break; default: break; } return ( branded ? 1 : 0 ); } std::string itname = item.name(DESC_PLAIN, false, false, false); lowercase(itname); // FIXME Maybe we should replace this with a test of ISFLAG_COSMETIC_MASK? const bool item_runed = itname.find("runed ") != std::string::npos; const bool heav_runed = itname.find("heavily ") != std::string::npos; const bool item_glows = itname.find("glowing") != std::string::npos; if (item_glows || item_runed && !heav_runed || get_equip_desc(item) == ISFLAG_EMBROIDERED_SHINY) { return 1; } // You can tell something is an artefact, because it'll have a // description which rules out anything else. // XXX: Fixedarts and unrandarts might upset the apple-cart, though. if (is_artefact(item)) return 2; return 0; } void item_check(bool verbose) { describe_floor(); origin_set(you.pos()); std::ostream& strm = msg::streams(MSGCH_FLOOR_ITEMS); std::vector items; item_list_on_square( items, igrd(you.pos()), true ); if (items.empty()) { if (verbose) strm << "There are no items here." << std::endl; return; } if (items.size() == 1 ) { item_def it(*items[0]); std::string name = get_menu_colour_prefix_tags(it, DESC_NOCAP_A); strm << "You see here " << name << '.' << std::endl; return; } bool done_init_line = false; if (static_cast(items.size()) >= Options.item_stack_summary_minimum) { std::vector item_chars; for (unsigned int i = 0; i < items.size() && i < 50; ++i) { glyph g = get_item_glyph(items[i]); get_item_glyph(items[i]); item_chars.push_back(g.ch * 0x100 + (10 - item_name_specialness(*(items[i])))); } std::sort(item_chars.begin(), item_chars.end()); std::string out_string = "Items here: "; int cur_state = -1; for (unsigned int i = 0; i < item_chars.size(); ++i) { const int specialness = 10 - (item_chars[i] % 0x100); if (specialness != cur_state) { switch (specialness) { case 2: out_string += ""; break; // artefact case 1: out_string += ""; break; // glowing/runed case 0: out_string += ""; break; // mundane } cur_state = specialness; } out_string += stringize_glyph(item_chars[i] / 0x100); if (i + 1 < item_chars.size() && (item_chars[i] / 0x100) != (item_chars[i+1] / 0x100)) { out_string += ' '; } } formatted_mpr(formatted_string::parse_string(out_string), MSGCH_FLOOR_ITEMS); done_init_line = true; } if (verbose || static_cast(items.size() + 1) < crawl_view.msgsz.y) { if (!done_init_line) strm << "Things that are here:" << std::endl; for (unsigned int i = 0; i < items.size(); ++i) { item_def it(*items[i]); std::string name = get_menu_colour_prefix_tags(it, DESC_NOCAP_A); strm << name << std::endl; } } else if (!done_init_line) strm << "There are many items here." << std::endl; if (items.size() > 5) learned_something_new(TUT_MULTI_PICKUP); } static void _pickup_menu(int item_link) { int n_did_pickup = 0; int n_tried_pickup = 0; std::vector items; item_list_on_square( items, item_link, false ); std::vector selected = select_items( items, "Select items to pick up" ); redraw_screen(); std::string pickup_warning; for (int i = 0, count = selected.size(); i < count; ++i) for (int j = item_link; j != NON_ITEM; j = mitm[j].link) { if (&mitm[j] == selected[i].item) { if (j == item_link) item_link = mitm[j].link; int num_to_take = selected[i].quantity; const bool take_all = (num_to_take == mitm[j].quantity); unsigned long oldflags = mitm[j].flags; mitm[j].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); int result = move_item_to_player( j, num_to_take ); // If we cleared any flags on the items, but the pickup was // partial, reset the flags for the items that remain on the // floor. if (result == 0 || result == -1) { n_tried_pickup++; if (result == 0) pickup_warning = "You can't carry that much weight."; else pickup_warning = "You can't carry that many items."; if (mitm[j].is_valid()) mitm[j].flags = oldflags; } else { n_did_pickup++; // If we deliberately chose to take only part of a // pile, we consider the rest to have been // "dropped." if (!take_all && mitm[j].is_valid()) mitm[j].flags |= ISFLAG_DROPPED; } } } if (!pickup_warning.empty()) { mpr(pickup_warning.c_str()); learned_something_new(TUT_HEAVY_LOAD); } if (n_did_pickup) you.turn_is_over = true; } bool origin_known(const item_def &item) { return (item.orig_place != 0); } void origin_reset(item_def &item) { item.orig_place = 0; item.orig_monnum = 0; } // We have no idea where the player found this item. void origin_set_unknown(item_def &item) { if (!origin_known(item)) { item.orig_place = 0xFFFF; item.orig_monnum = 0; } } // This item is starting equipment. void origin_set_startequip(item_def &item) { if (!origin_known(item)) { item.orig_place = 0xFFFF; item.orig_monnum = -IT_SRC_START; } } void _origin_set_portal_vault(item_def &item) { if (you.level_type != LEVEL_PORTAL_VAULT) return; item.props[PORTAL_VAULT_ORIGIN_KEY] = you.level_type_origin; } void origin_set_monster(item_def &item, const monsters *monster) { if (!origin_known(item)) { if (!item.orig_monnum) item.orig_monnum = monster->type + 1; item.orig_place = get_packed_place(); _origin_set_portal_vault(item); } } void origin_purchased(item_def &item) { // We don't need to check origin_known if it's a shop purchase item.orig_place = get_packed_place(); _origin_set_portal_vault(item); item.orig_monnum = -IT_SRC_SHOP; } void origin_acquired(item_def &item, int agent) { // We don't need to check origin_known if it's a divine gift item.orig_place = get_packed_place(); _origin_set_portal_vault(item); item.orig_monnum = -agent; } void origin_set_inventory(void (*oset)(item_def &item)) { for (int i = 0; i < ENDOFPACK; ++i) if (you.inv[i].is_valid()) oset(you.inv[i]); } static int _first_corpse_monnum(const coord_def& where) { // We could look for a corpse on this square and assume that the // items belonged to it, but that is unsatisfactory. // Actually, it would be easy to add the monster type to a corpse // (or to another item) by setting orig_monnum when the monster dies // (already done for unique monsters to get named zombies), but // personally, I rather like the way the player can't tell where an // item came from if he just finds it lying on the floor. (jpeg) return (0); } #ifdef DGL_MILESTONES static std::string _milestone_rune(const item_def &item) { return std::string("found ") + item.name(DESC_NOCAP_A) + "."; } static void _milestone_check(const item_def &item) { if (item.base_type == OBJ_MISCELLANY && item.sub_type == MISC_RUNE_OF_ZOT) { mark_milestone("rune", _milestone_rune(item)); } else if (item.base_type == OBJ_ORBS && item.sub_type == ORB_ZOT) { mark_milestone("orb", "found the Orb of Zot!"); } } #endif // DGL_MILESTONES static void _check_note_item(item_def &item) { if (item.flags & (ISFLAG_NOTED_GET | ISFLAG_NOTED_ID)) return; if (is_rune(item) || item.base_type == OBJ_ORBS || is_artefact(item)) { take_note(Note(NOTE_GET_ITEM, 0, 0, item.name(DESC_NOCAP_A).c_str(), origin_desc(item).c_str())); item.flags |= ISFLAG_NOTED_GET; // If it's already fully identified when picked up, don't take // further notes. if (fully_identified(item)) item.flags |= ISFLAG_NOTED_ID; } } void origin_set(const coord_def& where) { int monnum = _first_corpse_monnum(where); unsigned short pplace = get_packed_place(); for (stack_iterator si(where); si; ++si) { if (origin_known( *si )) continue; if (!si->orig_monnum) si->orig_monnum = static_cast( monnum ); si->orig_place = pplace; _origin_set_portal_vault(*si); #ifdef DGL_MILESTONES _milestone_check(*si); #endif } } void origin_set_monstercorpse(item_def &item, const coord_def& where) { item.orig_monnum = _first_corpse_monnum(where); } static void _origin_freeze(item_def &item, const coord_def& where) { if (!origin_known(item)) { if (!item.orig_monnum && where.x != -1 && where.y != -1) origin_set_monstercorpse(item, where); item.orig_place = get_packed_place(); _origin_set_portal_vault(item); _check_note_item(item); #ifdef DGL_MILESTONES _milestone_check(item); #endif } } std::string origin_monster_name(const item_def &item) { const int monnum = item.orig_monnum - 1; if (monnum == MONS_PLAYER_GHOST) return ("a player ghost"); else if (monnum == MONS_PANDEMONIUM_DEMON) return ("a demon"); return mons_type_name(monnum, DESC_NOCAP_A); } static std::string _origin_place_desc(const item_def &item) { if (place_type(item.orig_place) == LEVEL_PORTAL_VAULT && item.props.exists(PORTAL_VAULT_ORIGIN_KEY)) { return item.props[PORTAL_VAULT_ORIGIN_KEY].get_string(); } return prep_branch_level_name(item.orig_place); } bool is_rune(const item_def &item) { return (item.base_type == OBJ_MISCELLANY && item.sub_type == MISC_RUNE_OF_ZOT); } bool is_unique_rune(const item_def &item) { return (item.base_type == OBJ_MISCELLANY && item.sub_type == MISC_RUNE_OF_ZOT && item.plus != RUNE_DEMONIC && item.plus != RUNE_ABYSSAL); } bool origin_describable(const item_def &item) { return (origin_known(item) && (item.orig_place != 0xFFFFU || item.orig_monnum == -1) && (!is_stackable_item(item) || is_rune(item)) && item.quantity == 1 && item.base_type != OBJ_CORPSES && (item.base_type != OBJ_FOOD || item.sub_type != FOOD_CHUNK)); } static std::string _article_it(const item_def &item) { // "it" is always correct, since gloves and boots also come in pairs. return "it"; } static bool _origin_is_original_equip(const item_def &item) { return (item.orig_place == 0xFFFFU && item.orig_monnum == -IT_SRC_START); } bool origin_is_god_gift(const item_def& item, god_type *god) { god_type junk; if (god == NULL) god = &junk; *god = GOD_NO_GOD; const int iorig = -item.orig_monnum; if (iorig > GOD_NO_GOD && iorig < NUM_GODS) { *god = static_cast(iorig); return (true); } return (false); } bool origin_is_acquirement(const item_def& item, item_source_type *type) { item_source_type junk; if (type == NULL) type = &junk; *type = IT_SRC_NONE; const int iorig = -item.orig_monnum; if (iorig == AQ_SCROLL || iorig == AQ_CARD_GENIE || iorig == AQ_WIZMODE) { *type = static_cast(iorig); return (true); } return (false); } std::string origin_desc(const item_def &item) { if (!origin_describable(item)) return (""); if (_origin_is_original_equip(item)) return "Original Equipment"; std::string desc; if (item.orig_monnum) { if (item.orig_monnum < 0) { int iorig = -item.orig_monnum; switch (iorig) { case IT_SRC_SHOP: desc += "You bought " + _article_it(item) + " in a shop "; break; case IT_SRC_START: desc += "Buggy Original Equipment: "; break; case AQ_SCROLL: desc += "You acquired " + _article_it(item) + " "; break; case AQ_CARD_GENIE: desc += "You drew the Genie "; break; case AQ_WIZMODE: desc += "Your wizardly powers created "+ _article_it(item)+ " "; break; default: if (iorig > GOD_NO_GOD && iorig < NUM_GODS) { desc += god_name(static_cast(iorig)) + " gifted " + _article_it(item) + " to you "; } else { // Bug really. desc += "You stumbled upon " + _article_it(item) + " "; } break; } } else if (item.orig_monnum - 1 == MONS_DANCING_WEAPON) desc += "You subdued it "; else { desc += "You took " + _article_it(item) + " off " + origin_monster_name(item) + " "; } } else desc += "You found " + _article_it(item) + " "; desc += _origin_place_desc(item); return (desc); } bool pickup_single_item(int link, int qty) { if (you.flight_mode() == FL_LEVITATE) { mpr("You can't reach the floor from up here."); return (false); } if (qty < 1 || qty > mitm[link].quantity) qty = mitm[link].quantity; unsigned long oldflags = mitm[link].flags; mitm[link].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); int num = move_item_to_player( link, qty ); if (mitm[link].is_valid()) mitm[link].flags = oldflags; if (num == -1) { mpr("You can't carry that many items."); learned_something_new(TUT_HEAVY_LOAD); return (false); } else if (num == 0) { mpr("You can't carry that much weight."); learned_something_new(TUT_HEAVY_LOAD); return (false); } return (true); } void pickup() { int keyin = 'x'; if (you.flight_mode() == FL_LEVITATE) { mpr("You can't reach the floor from up here."); return; } int o = you.visible_igrd(you.pos()); const int num_nonsquelched = _count_nonsquelched_items(o); if (o == NON_ITEM) { mpr("There are no items here."); } else if (mitm[o].link == NON_ITEM) // just one item? { // Deliberately allowing the player to pick up // a killed item here. pickup_single_item(o, mitm[o].quantity); } else if (Options.pickup_mode != -1 && num_nonsquelched >= Options.pickup_mode) { _pickup_menu(o); } else { int next; mpr("There are several objects here."); std::string pickup_warning; while (o != NON_ITEM) { // Must save this because pickup can destroy the item. next = mitm[o].link; if (num_nonsquelched && _invisible_to_player(mitm[o])) { o = next; continue; } if (keyin != 'a') { std::string prompt = "Pick up %s? (" #ifdef USE_TILE "Left-click to enter menu, or press " #endif "y/n/a/*?g,/q/o)"; mprf(MSGCH_PROMPT, prompt.c_str(), get_menu_colour_prefix_tags(mitm[o], DESC_NOCAP_A).c_str()); mouse_control mc(MOUSE_MODE_MORE); keyin = getch(); } if (keyin == '*' || keyin == '?' || keyin == ',' || keyin == 'g' || keyin == CK_MOUSE_CLICK) { _pickup_menu(o); break; } if (keyin == 'q' || keyin == ESCAPE || keyin == 'o') break; if (keyin == 'y' || keyin == 'a') { int num_to_take = mitm[o].quantity; const unsigned long old_flags(mitm[o].flags); mitm[o].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); int result = move_item_to_player( o, num_to_take ); if (result == 0 || result == -1) { if (result == 0) pickup_warning = "You can't carry that much weight."; else pickup_warning = "You can't carry that many items."; mitm[o].flags = old_flags; } } o = next; } if (!pickup_warning.empty()) mpr(pickup_warning.c_str()); if (keyin == 'o') start_explore(Options.explore_greedy); } } bool is_stackable_item( const item_def &item ) { if (!item.is_valid()) return (false); if (item.base_type == OBJ_MISSILES || item.base_type == OBJ_FOOD || item.base_type == OBJ_SCROLLS || item.base_type == OBJ_POTIONS || item.base_type == OBJ_UNKNOWN_II || item.base_type == OBJ_GOLD || (item.base_type == OBJ_MISCELLANY && item.sub_type == MISC_RUNE_OF_ZOT)) { return (true); } return (false); } unsigned long ident_flags(const item_def &item) { const unsigned long identmask = full_ident_mask(item); unsigned long flags = item.flags & identmask; if ((identmask & ISFLAG_KNOW_TYPE) && item_type_known(item)) flags |= ISFLAG_KNOW_TYPE; return (flags); } bool items_similar(const item_def &item1, const item_def &item2, bool ignore_ident) { // Base and sub-types must always be the same to stack. if (item1.base_type != item2.base_type || item1.sub_type != item2.sub_type) return (false); if (item1.base_type == OBJ_GOLD) return (true); // These classes also require pluses and special. if (item1.base_type == OBJ_WEAPONS // only throwing weapons || item1.base_type == OBJ_MISSILES || item1.base_type == OBJ_MISCELLANY // only runes || item1.base_type == OBJ_FOOD) // chunks { if (item1.plus != item2.plus || item1.plus2 != item2.plus2 || ((item1.base_type == OBJ_FOOD && item2.sub_type == FOOD_CHUNK) ? // Reject chunk merge if chunk ages differ by more than 5 abs(item1.special - item2.special) > 5 // Non-chunk item specials must match exactly. : item1.special != item2.special)) { return (false); } } // Check the ID flags. if (!ignore_ident && ident_flags(item1) != ident_flags(item2)) return (false); // Check the non-ID flags, but ignore dropped, thrown, cosmetic, // and note flags. Also, whether item was in inventory before. #define NON_IDENT_FLAGS ~(ISFLAG_IDENT_MASK | ISFLAG_COSMETIC_MASK | \ ISFLAG_DROPPED | ISFLAG_THROWN | \ ISFLAG_NOTED_ID | ISFLAG_NOTED_GET | \ ISFLAG_BEEN_IN_INV | ISFLAG_DROPPED_BY_ALLY) if ((item1.flags & NON_IDENT_FLAGS) != (item2.flags & NON_IDENT_FLAGS)) return (false); if (item1.base_type == OBJ_POTIONS) { // Thanks to mummy cursing, we can have potions of decay // that don't look alike... so we don't stack potions // if either isn't identified and they look different. -- bwr if (item1.plus != item2.plus && (!item_type_known(item1) || !item_type_known(item2))) { return (false); } } // The inscriptions can differ if one of them is blank, but if they // are differing non-blank inscriptions then don't stack. if (item1.inscription != item2.inscription && !item1.inscription.empty() && !item2.inscription.empty()) { return (false); } return (true); } bool items_stack( const item_def &item1, const item_def &item2, bool force_merge, bool ignore_ident ) { // Both items must be stackable. if (!force_merge && (!is_stackable_item( item1 ) || !is_stackable_item( item2 ))) { return (false); } return items_similar(item1, item2, ignore_ident); } void merge_item_stacks(item_def &source, item_def &dest, int quant) { if (quant == -1) quant = source.quantity; ASSERT(quant > 0 && quant <= source.quantity); if (is_blood_potion(source) && is_blood_potion(dest)) merge_blood_potion_stacks(source, dest, quant); } static int _userdef_find_free_slot(const item_def &i) { #ifdef CLUA_BINDINGS int slot = -1; if (!clua.callfn("c_assign_invletter", "u>d", &i, &slot)) return (-1); return (slot); #else return -1; #endif } int find_free_slot(const item_def &i) { #define slotisfree(s) \ ((s) >= 0 && (s) < ENDOFPACK && !you.inv[s].is_valid()) bool searchforward = false; // If we're doing Lua, see if there's a Lua function that can give // us a free slot. int slot = _userdef_find_free_slot(i); if (slot == -2 || Options.assign_item_slot == SS_FORWARD) searchforward = true; if (slotisfree(slot)) return slot; // See if the item remembers where it's been. Lua code can play with // this field so be extra careful. if (i.slot >= 'a' && i.slot <= 'z' || i.slot >= 'A' && i.slot <= 'Z') { slot = letter_to_index(i.slot); } if (slotisfree(slot)) return slot; int disliked = -1; if (i.base_type == OBJ_FOOD) disliked = 'e' - 'a'; else if (i.base_type == OBJ_POTIONS) disliked = 'y' - 'a'; if (!searchforward) { // This is the new default free slot search. We look for the last // available slot that does not leave a gap in the inventory. for (slot = ENDOFPACK - 1; slot >= 0; --slot) { if (you.inv[slot].is_valid()) { if (slot + 1 < ENDOFPACK && !you.inv[slot + 1].is_valid() && slot + 1 != disliked) { return (slot + 1); } } else { if (slot + 1 < ENDOFPACK && you.inv[slot + 1].is_valid() && slot != disliked) { return (slot); } } } } // Either searchforward is true, or search backwards failed and // we re-try searching the oposite direction. // Return first free slot for (slot = 0; slot < ENDOFPACK; ++slot) if (slot != disliked && !you.inv[slot].is_valid()) return slot; // If the least preferred slot is the only choice, so be it. if (disliked != -1 && !you.inv[disliked].is_valid()) return disliked; return (-1); #undef slotisfree } static void _got_item(item_def& item, int quant) { seen_item(item); shopping_list.cull_identical_items(item); if (!is_rune(item)) return; // Picking up the rune for the first time. if (!(item.flags & ISFLAG_BEEN_IN_INV)) { if (is_unique_rune(item)) you.attribute[ATTR_UNIQUE_RUNES] += quant; else if (item.plus == RUNE_ABYSSAL) you.attribute[ATTR_ABYSSAL_RUNES] += quant; else you.attribute[ATTR_DEMONIC_RUNES] += quant; } item.flags |= ISFLAG_BEEN_IN_INV; _check_note_item(item); } void note_inscribe_item(item_def &item) { _autoinscribe_item(item); _origin_freeze(item, you.pos()); _check_note_item(item); } // Returns quantity of items moved into player's inventory and -1 if // the player's inventory is full. int move_item_to_player(int obj, int quant_got, bool quiet, bool ignore_burden) { if (item_is_stationary(mitm[obj])) { mpr("You cannot pick up the net that holds you!"); // Fake a successful pickup (return 1), so we can continue to // pick up anything else that might be on this square. return (1); } int retval = quant_got; // Gold has no mass, so we handle it first. if (mitm[obj].base_type == OBJ_GOLD) { you.attribute[ATTR_GOLD_FOUND] += quant_got; you.add_gold(quant_got); dec_mitm_item_quantity(obj, quant_got); if (!quiet) { mprf("You now have %d gold piece%s.", you.gold, you.gold != 1 ? "s" : ""); } learned_something_new(TUT_SEEN_GOLD); you.turn_is_over = true; return (retval); } const int unit_mass = item_mass( mitm[obj] ); if (quant_got > mitm[obj].quantity || quant_got <= 0) quant_got = mitm[obj].quantity; const int imass = unit_mass * quant_got; bool partial_pickup = false; if (!ignore_burden && (you.burden + imass > carrying_capacity())) { // calculate quantity we can actually pick up int part = (carrying_capacity() - you.burden) / unit_mass; if (part < 1) return (0); // only pickup 'part' items quant_got = part; partial_pickup = true; retval = part; } if (is_stackable_item( mitm[obj] )) { for (int m = 0; m < ENDOFPACK; m++) { if (items_stack( you.inv[m], mitm[obj], false, true )) { if (!quiet && partial_pickup) mpr("You can only carry some of what is here."); _check_note_item(mitm[obj]); const bool floor_god_gift = mitm[obj].inscription.find("god gift") != std::string::npos; const bool inv_god_gift = you.inv[m].inscription.find("god gift") != std::string::npos; // If the object on the ground is inscribed, but not // the one in inventory, then the inventory object // picks up the other's inscription. if (!(mitm[obj].inscription).empty() && you.inv[m].inscription.empty()) { you.inv[m].inscription = mitm[obj].inscription; } // Remove god gift inscription unless both items have it. if (floor_god_gift && !inv_god_gift || inv_god_gift && !floor_god_gift) { you.inv[m].inscription = replace_all(you.inv[m].inscription, "god gift, ", ""); you.inv[m].inscription = replace_all(you.inv[m].inscription, "god gift", ""); } // If only one of the stacks is identified, // identify the other to a similar extent. if (ident_flags(mitm[obj]) != ident_flags(you.inv[m])) { if (!quiet) mpr("These items seem quite similar."); mitm[obj].flags |= ident_flags(you.inv[m]) & you.inv[m].flags; you.inv[m].flags |= ident_flags(mitm[obj]) & mitm[obj].flags; } merge_item_stacks(mitm[obj], you.inv[m], quant_got); inc_inv_item_quantity( m, quant_got ); dec_mitm_item_quantity( obj, quant_got ); burden_change(); _got_item(mitm[obj], quant_got); if (!quiet) { mpr(get_menu_colour_prefix_tags(you.inv[m], DESC_INVENTORY).c_str()); } you.turn_is_over = true; return (retval); } } } // Can't combine, check for slot space. if (inv_count() >= ENDOFPACK) return (-1); if (!quiet && partial_pickup) mpr("You can only carry some of what is here."); int freeslot = find_free_slot(mitm[obj]); if (freeslot < 0 || freeslot >= ENDOFPACK || you.inv[freeslot].is_valid()) { // Something is terribly wrong. return (-1); } coord_def p = mitm[obj].pos; // If moving an item directly from a monster to the player without the // item having been on the grid, then it really isn't a position event. if (in_bounds(p)) { dungeon_events.fire_position_event( dgn_event(DET_ITEM_PICKUP, p, 0, obj, -1), p); } item_def &item = you.inv[freeslot]; // Copy item. item = mitm[obj]; item.link = freeslot; item.pos.set(-1, -1); // Remove "dropped by ally" flag. item.flags &= ~(ISFLAG_DROPPED_BY_ALLY); if (!item.slot) item.slot = index_to_letter(item.link); note_inscribe_item(item); item.quantity = quant_got; if (is_blood_potion(mitm[obj])) { if (quant_got != mitm[obj].quantity) { // Remove oldest timers from original stack. for (int i = 0; i < quant_got; i++) remove_oldest_blood_potion(mitm[obj]); // ... and newest ones from picked up stack remove_newest_blood_potion(item); } } dec_mitm_item_quantity( obj, quant_got ); you.m_quiver->on_inv_quantity_changed(freeslot, quant_got); burden_change(); if (!quiet) { mpr(get_menu_colour_prefix_tags(you.inv[freeslot], DESC_INVENTORY).c_str()); } if (Tutorial.tutorial_left) { taken_new_item(item.base_type); if (is_artefact(item) || get_equip_desc( item ) != ISFLAG_NO_DESC) learned_something_new(TUT_SEEN_RANDART); } if (item.base_type == OBJ_ORBS && you.char_direction == GDT_DESCENDING) { // Take a note! _check_note_item(item); if (!quiet) mpr("Now all you have to do is get back out of the dungeon!"); you.char_direction = GDT_ASCENDING; xom_is_stimulated(255, XM_INTRIGUED); } if (item.base_type == OBJ_ORBS && you.level_type == LEVEL_DUNGEON) unset_branch_flags(BFLAG_HAS_ORB); _got_item(item, item.quantity); you.turn_is_over = true; return (retval); } void mark_items_non_pickup_at(const coord_def &pos) { int item = igrd(pos); while (item != NON_ITEM) { mitm[item].flags |= ISFLAG_DROPPED; mitm[item].flags &= ~ISFLAG_THROWN; item = mitm[item].link; } } // Moves mitm[obj] to p... will modify the value of obj to // be the index of the final object (possibly different). // // Done this way in the hopes that it will be obvious from // calling code that "obj" is possibly modified. // // Returns false on error or level full - cases where you // keep the item. bool move_item_to_grid( int *const obj, const coord_def& p, bool silent ) { ASSERT(in_bounds(p)); int& ob(*obj); // Must be a valid reference to a valid object. if (ob == NON_ITEM || !mitm[ob].is_valid()) return (false); item_def& item(mitm[ob]); if (feat_destroys_item(grd(p), mitm[ob], !silenced(p) && !silent)) { item_was_destroyed(item, NON_MONSTER); destroy_item(ob); ob = NON_ITEM; return (true); } // If it's a stackable type... if (is_stackable_item( item )) { // Look for similar item to stack: for (stack_iterator si(p); si; ++si) { // Check if item already linked here -- don't want to unlink it. if (ob == si->index()) return (false); if (items_stack( item, *si )) { // Add quantity to item already here, and dispose // of obj, while returning the found item. -- bwr inc_mitm_item_quantity( si->index(), item.quantity ); merge_item_stacks(item, *si); destroy_item( ob ); ob = si->index(); return (true); } } } // Non-stackable item that's been fudge-stacked (monster throwing weapons). // Explode the stack when dropped. We have to special case chunks, ew. else if (item.quantity > 1 && (item.base_type != OBJ_FOOD || item.sub_type != FOOD_CHUNK)) { while (item.quantity > 1) { // If we can't copy the items out, we lose the surplus. if (copy_item_to_grid(item, p, 1, false, true)) --item.quantity; else item.quantity = 1; } } ASSERT( ob != NON_ITEM ); // Need to actually move object, so first unlink from old position. unlink_item( ob ); // Move item to coord. item.pos = p; // Link item to top of list. item.link = igrd(p); igrd(p) = ob; if (item.base_type == OBJ_ORBS && you.level_type == LEVEL_DUNGEON) set_branch_flags(BFLAG_HAS_ORB); return (true); } void move_item_stack_to_grid( const coord_def& from, const coord_def& to ) { if (igrd(from) == NON_ITEM) return; // Tell all items in stack what the new coordinate is. for (stack_iterator si(from); si; ++si) { si->pos = to; // Link last of the stack to the top of the old stack. if (si->link == NON_ITEM && igrd(to) != NON_ITEM) { si->link = igrd(to); break; } } igrd(to) = igrd(from); igrd(from) = NON_ITEM; } // Returns false iff no items could be dropped. bool copy_item_to_grid( const item_def &item, const coord_def& p, int quant_drop, bool mark_dropped, bool silent ) { ASSERT(in_bounds(p)); if (quant_drop == 0) return (false); if (feat_destroys_item(grd(p), item, !silenced(p) && !silent)) { item_was_destroyed(item, NON_MONSTER); return (true); } // default quant_drop == -1 => drop all if (quant_drop < 0) quant_drop = item.quantity; // Loop through items at current location. if (is_stackable_item( item )) { for (stack_iterator si(p); si; ++si) { if (items_stack( item, *si )) { inc_mitm_item_quantity( si->index(), quant_drop ); item_def copy = item; merge_item_stacks(copy, *si, quant_drop); // If the items on the floor already have a nonzero slot, // leave it as such, otherwise set the slot. if (mark_dropped && !si->slot) si->slot = index_to_letter(item.link); return (true); } } } // Item not found in current stack, add new item to top. int new_item_idx = get_item_slot(10); if (new_item_idx == NON_ITEM) return (false); item_def& new_item = mitm[new_item_idx]; // Copy item. new_item = item; // Set quantity, and set the item as unlinked. new_item.quantity = quant_drop; new_item.pos.reset(); new_item.link = NON_ITEM; if (mark_dropped) { new_item.slot = index_to_letter(item.link); new_item.flags |= ISFLAG_DROPPED; new_item.flags &= ~ISFLAG_THROWN; origin_set_unknown(new_item); } move_item_to_grid( &new_item_idx, p, true ); if (is_blood_potion(item) && item.quantity != quant_drop) // partial drop only { // Since only the oldest potions have been dropped, // remove the newest ones. remove_newest_blood_potion(new_item); } return (true); } //--------------------------------------------------------------- // // move_top_item -- moves the top item of a stack to a new // location. // //--------------------------------------------------------------- bool move_top_item( const coord_def &pos, const coord_def &dest ) { int item = igrd(pos); if (item == NON_ITEM) return (false); dungeon_events.fire_position_event( dgn_event(DET_ITEM_MOVED, pos, 0, item, -1, dest), pos); // Now move the item to its new possition... move_item_to_grid( &item, dest ); return (true); } bool drop_item( int item_dropped, int quant_drop, bool try_offer ) { if (quant_drop < 0 || quant_drop > you.inv[item_dropped].quantity) quant_drop = you.inv[item_dropped].quantity; if (item_dropped == you.equip[EQ_LEFT_RING] || item_dropped == you.equip[EQ_RIGHT_RING] || item_dropped == you.equip[EQ_AMULET]) { if (!Options.easy_unequip) { mpr("You will have to take that off first."); return (false); } if (remove_ring( item_dropped, true )) start_delay( DELAY_DROP_ITEM, 1, item_dropped, 1 ); return (false); } if (item_dropped == you.equip[EQ_WEAPON] && you.inv[item_dropped].base_type == OBJ_WEAPONS && you.inv[item_dropped] .cursed()) { mpr("That object is stuck to you!"); return (false); } for (int i = EQ_CLOAK; i <= EQ_BODY_ARMOUR; i++) { if (item_dropped == you.equip[i] && you.equip[i] != -1) { if (!Options.easy_unequip) { mpr("You will have to take that off first."); } else if (check_warning_inscriptions(you.inv[item_dropped], OPER_TAKEOFF)) { // If we take off the item, cue up the item being dropped if (takeoff_armour( item_dropped )) { start_delay( DELAY_DROP_ITEM, 1, item_dropped, 1 ); you.turn_is_over = false; // turn happens later } } // Regardless, we want to return here because either we're // aborting the drop, or the drop is delayed until after // the armour is removed. -- bwr return (false); } } // [ds] easy_unequip does not apply to weapons. // // Unwield needs to be done before copy in order to clear things // like temporary brands. -- bwr if (item_dropped == you.equip[EQ_WEAPON] && quant_drop >= you.inv[item_dropped].quantity) { if (!wield_weapon(true, PROMPT_GOT_SPECIAL)) return (false); } const dungeon_feature_type my_grid = grd(you.pos()); if (!copy_item_to_grid( you.inv[item_dropped], you.pos(), quant_drop, true, true )) { mpr("Too many items on this level, not dropping the item."); return (false); } mprf("You drop %s.", quant_name(you.inv[item_dropped], quant_drop, DESC_NOCAP_A).c_str()); bool quiet = silenced(you.pos()); // If you drop an item in as a merfolk, it is below the water line and // makes no noise falling. if (you.swimming()) quiet = true; if (feat_destroys_item(my_grid, you.inv[item_dropped], !quiet)) ; else if (strstr(you.inv[item_dropped].inscription.c_str(), "=s") != 0) StashTrack.add_stash(); if (is_blood_potion(you.inv[item_dropped]) && you.inv[item_dropped].quantity != quant_drop) { // Oldest potions have been dropped. for (int i = 0; i < quant_drop; i++) remove_oldest_blood_potion(you.inv[item_dropped]); } dec_inv_item_quantity( item_dropped, quant_drop ); you.turn_is_over = true; if (try_offer && you.religion != GOD_NO_GOD && you.duration[DUR_PRAYER] && feat_altar_god(grd(you.pos())) == you.religion) { offer_items(); } return (true); } static std::string _drop_menu_invstatus(const Menu *menu) { const int cap = carrying_capacity(BS_UNENCUMBERED); std::string s_newweight; std::vector se = menu->selected_entries(); if (!se.empty()) { int newweight = you.burden; for (int i = 0, size = se.size(); i < size; ++i) { const item_def *item = static_cast( se[i]->data ); newweight -= item_mass(*item) * se[i]->selected_qty; } s_newweight = make_stringf(">%.0f", newweight * BURDEN_TO_AUM); } return (make_stringf("(Inv: %.0f%s/%.0f aum)", you.burden * BURDEN_TO_AUM, s_newweight.c_str(), cap * BURDEN_TO_AUM)); } static std::string _drop_menu_title(const Menu *menu, const std::string &oldt) { std::string res = _drop_menu_invstatus(menu) + " " + oldt; if (menu->is_set( MF_MULTISELECT )) res = "[Multidrop] " + res; return (res); } int get_equip_slot(const item_def *item) { int worn = -1; if (item && in_inventory(*item)) { for (int i = 0; i < NUM_EQUIP; ++i) { if (you.equip[i] == item->link) { worn = i; break; } } } return worn; } mon_inv_type get_mon_equip_slot(const monsters* mon, const item_def &item) { ASSERT(mon->alive()); mon_inv_type slot = item_to_mslot(item); if (slot == NUM_MONSTER_SLOTS) return NUM_MONSTER_SLOTS; if (mon->mslot_item(slot) == &item) return slot; if (slot == MSLOT_WEAPON && mon->mslot_item(MSLOT_ALT_WEAPON) == &item) return MSLOT_ALT_WEAPON; return NUM_MONSTER_SLOTS; } static std::string _drop_selitem_text( const std::vector *s ) { bool extraturns = false; if (s->empty()) return ""; for (int i = 0, size = s->size(); i < size; ++i) { const item_def *item = static_cast( (*s)[i]->data ); const int eq = get_equip_slot(item); if (eq > EQ_WEAPON && eq < NUM_EQUIP) { extraturns = true; break; } } return (make_stringf( " (%lu%s turn%s)", (unsigned long) (s->size()), extraturns? "+" : "", s->size() > 1? "s" : "" )); } std::vector items_for_multidrop; // Arrange items that have been selected for multidrop so that // equipped items are dropped after other items, and equipped items // are dropped in the same order as their EQ_ slots are numbered. static bool _drop_item_order(const SelItem &first, const SelItem &second) { const item_def &i1 = you.inv[first.slot]; const item_def &i2 = you.inv[second.slot]; const int slot1 = get_equip_slot(&i1), slot2 = get_equip_slot(&i2); if (slot1 != -1 && slot2 != -1) return (slot1 < slot2); else if (slot1 != -1 && slot2 == -1) return (false); else if (slot2 != -1 && slot1 == -1) return (true); return (first.slot < second.slot); } //--------------------------------------------------------------- // // drop // // Prompts the user for an item to drop // //--------------------------------------------------------------- void drop() { if (inv_count() < 1 && you.gold == 0) { canned_msg(MSG_NOTHING_CARRIED); return; } std::vector tmp_items; tmp_items = prompt_invent_items( "Drop what?", MT_DROP, -1, _drop_menu_title, true, true, 0, &Options.drop_filter, _drop_selitem_text, &items_for_multidrop ); if (tmp_items.empty()) { canned_msg( MSG_OK ); return; } // Sort the dropped items so we don't see weird behaviour when // dropping a worn robe before a cloak (old behaviour: remove // cloak, remove robe, wear cloak, drop robe, remove cloak, drop // cloak). std::sort( tmp_items.begin(), tmp_items.end(), _drop_item_order ); // If the user answers "no" to an item an with a warning inscription, // then remove it from the list of items to drop by not copying it // over to items_for_multidrop. items_for_multidrop.clear(); for (unsigned int i = 0; i < tmp_items.size(); ++i) { SelItem& si(tmp_items[i]); const int item_quant = si.item->quantity; // EVIL HACK: Fix item quantity to match the quantity we will drop, // in order to prevent misleading messages when dropping // 15 of 25 arrows inscribed with {!d}. if (si.quantity && si.quantity != item_quant) const_cast(si.item)->quantity = si.quantity; // Check if we can add it to the multidrop list. bool warning_ok = check_warning_inscriptions(*(si.item), OPER_DROP); // Restore the item quantity if we mangled it. if (item_quant != si.item->quantity) const_cast(si.item)->quantity = item_quant; if (warning_ok) items_for_multidrop.push_back(si); } if (items_for_multidrop.empty()) // no items. { canned_msg( MSG_OK ); return; } if (items_for_multidrop.size() == 1) // only one item { drop_item( items_for_multidrop[0].slot, items_for_multidrop[0].quantity, true ); items_for_multidrop.clear(); you.turn_is_over = true; } else start_delay( DELAY_MULTIDROP, items_for_multidrop.size() ); } static void _autoinscribe_item( item_def& item ) { // If there's an inscription already, do nothing - except // for automatically generated inscriptions if (!item.inscription.empty() && item.inscription != "god gift") return; const std::string old_inscription = item.inscription; item.inscription.clear(); std::string iname = _autopickup_item_name(item); for (unsigned i = 0; i < Options.autoinscriptions.size(); ++i) { if (Options.autoinscriptions[i].first.matches(iname)) { // Don't autoinscribe dropped items on ground with // "=g". If the item matches a rule which adds "=g", // "=g" got added to it before it was dropped, and // then the user explictly removed it because they // don't want to autopickup it again. std::string str = Options.autoinscriptions[i].second; if ((item.flags & ISFLAG_DROPPED) && !in_inventory(item)) str = replace_all(str, "=g", ""); // Note that this might cause the item inscription to // pass 80 characters. item.inscription += str; } } if (!old_inscription.empty()) { if (item.inscription.empty()) item.inscription = old_inscription; else item.inscription = old_inscription + ", " + item.inscription; } } static void _autoinscribe_floor_items() { for (stack_iterator si(you.pos()); si; ++si) _autoinscribe_item( *si ); } static void _autoinscribe_inventory() { for (int i = 0; i < ENDOFPACK; i++) if (you.inv[i].is_valid()) _autoinscribe_item( you.inv[i] ); } bool need_to_autoinscribe() { return will_autoinscribe; } void request_autoinscribe(bool do_inscribe) { will_autoinscribe = do_inscribe; } void autoinscribe() { _autoinscribe_floor_items(); _autoinscribe_inventory(); will_autoinscribe = false; } static inline std::string _autopickup_item_name(const item_def &item) { return userdef_annotate_item(STASH_LUA_SEARCH_ANNOTATE, &item, true) + menu_colour_item_prefix(item, false) + " " + item.name(DESC_PLAIN); } static bool _is_option_autopickup(const item_def &item, std::string &iname) { if (iname.empty()) iname = _autopickup_item_name(item); for (unsigned i = 0, size = Options.force_autopickup.size(); i < size; ++i) if (Options.force_autopickup[i].first.matches(iname)) return Options.force_autopickup[i].second; #ifdef CLUA_BINDINGS if (clua.callbooleanfn(false, "ch_force_autopickup", "us", &item, iname.c_str())) { return (true); } #endif return (Options.autopickups & (1L << item.base_type)); } bool item_needs_autopickup(const item_def &item) { if (item_is_stationary(item)) return (false); if (item.inscription.find("=g") != std::string::npos) return (true); if ((item.flags & ISFLAG_THROWN) && Options.pickup_thrown) return (true); if ((item.flags & ISFLAG_DROPPED) && !Options.pickup_dropped) return (false); std::string itemname; return _is_option_autopickup(item, itemname); } bool can_autopickup() { // [ds] Checking for autopickups == 0 is a bad idea because // autopickup is still possible with inscriptions and // pickup_thrown. if (Options.autopickup_on <= 0) return (false); if (you.flight_mode() == FL_LEVITATE) return (false); if (!i_feel_safe()) return (false); return (true); } typedef bool (*item_comparer)(const item_def& pickup_item, const item_def& inv_item); static bool _identical_types(const item_def& pickup_item, const item_def& inv_item) { return ((pickup_item.base_type == inv_item.base_type) && (pickup_item.sub_type == inv_item.sub_type)); } static bool _edible_food(const item_def& pickup_item, const item_def& inv_item) { return (inv_item.base_type == OBJ_FOOD && !is_inedible(inv_item)); } static bool _similar_equip(const item_def& pickup_item, const item_def& inv_item) { const equipment_type inv_slot = get_item_slot(inv_item); if (inv_slot == EQ_NONE) return (false); // If it's an unequipped cursed item the player might be looking // for a replacement. if (item_known_cursed(inv_item) && !item_is_equipped(inv_item)) return (false); if (get_item_slot(pickup_item) != inv_slot) return (false); // Just filling the same slot is enough for armour to be considered // similar. if (pickup_item.base_type == OBJ_ARMOUR) return (true); // Launchers of the same type are similar. if ((pickup_item.sub_type >= WPN_BLOWGUN && pickup_item.sub_type <= WPN_LONGBOW) || pickup_item.sub_type == WPN_SLING) { return (pickup_item.sub_type != inv_item.sub_type); } return ((weapon_skill(pickup_item) == weapon_skill(inv_item)) && (get_damage_type(pickup_item) == get_damage_type(inv_item))); } static bool _similar_wands(const item_def& pickup_item, const item_def& inv_item) { if (inv_item.base_type != OBJ_WANDS) return (false); if (pickup_item.sub_type != inv_item.sub_type) return (false); // Not similar if wand in inventory is known to be empty. return (inv_item.plus2 != ZAPCOUNT_EMPTY || (item_ident(inv_item, ISFLAG_KNOW_PLUSES) && inv_item.plus > 0)); } static bool _similar_jewellery(const item_def& pickup_item, const item_def& inv_item) { if (inv_item.base_type != OBJ_JEWELLERY) return (false); if (pickup_item.sub_type != inv_item.sub_type) return (false); // For jewellery of the same sub-type, unidentified jewellery is // always considered similar, as is identified jewellery whose // effect doesn't stack. return (!item_type_known(inv_item) || (!jewellery_is_amulet(inv_item) && !ring_has_stackable_effect(inv_item))); } static bool _item_different_than_inv(const item_def& pickup_item, item_comparer comparer) { for (int i = 0; i < ENDOFPACK; i++) { const item_def& inv_item(you.inv[i]); if (!inv_item.is_valid()) continue; if ( (*comparer)(pickup_item, inv_item) ) return (false); } return (true); } static bool _interesting_explore_pickup(const item_def& item) { if (!(Options.explore_stop & ES_GREEDY_PICKUP_MASK)) return (false); // Gold is boring. if (item.base_type == OBJ_GOLD) return (false); if ((Options.explore_stop & ES_GREEDY_PICKUP_THROWN) && (item.flags & ISFLAG_THROWN)) { return (true); } std::vector &ignore = Options.explore_stop_pickup_ignore; if (!ignore.empty()) { const std::string name = item.name(DESC_PLAIN); for (unsigned int i = 0; i < ignore.size(); i++) if (ignore[i].matches(name)) return (false); } if (!(Options.explore_stop & ES_GREEDY_PICKUP_SMART)) return (true); // "Smart" code follows. // If ES_GREEDY_PICKUP_THROWN isn't set, then smart greedy pickup // will ignore thrown items. if (item.flags & ISFLAG_THROWN) return (false); if (is_artefact(item)) return (true); // Possbible ego items. if (!item_type_known(item) & (item.flags & ISFLAG_COSMETIC_MASK)) return (true); switch(item.base_type) { case OBJ_WEAPONS: case OBJ_MISSILES: case OBJ_ARMOUR: // Ego items. if (item.special != 0) return (true); break; default: break; } switch(item.base_type) { case OBJ_WEAPONS: case OBJ_ARMOUR: return _item_different_than_inv(item, _similar_equip); case OBJ_WANDS: return _item_different_than_inv(item, _similar_wands); case OBJ_JEWELLERY: return _item_different_than_inv(item, _similar_jewellery); case OBJ_FOOD: if (you.religion == GOD_FEDHAS && is_fruit(item)) return (true); if (is_inedible(item)) return (false); // Interesting if we don't have any other edible food. return _item_different_than_inv(item, _edible_food); case OBJ_STAVES: // Rods are always interesting, even if you already have one of // the same type, since each rod has its own mana. if (item_is_rod(item)) return (true); // Intentional fall-through. case OBJ_MISCELLANY: // Runes are always interesting. if (is_rune(item)) return (true); // Decks always start out unidentified. if (is_deck(item)) return (true); // Intentional fall-through. case OBJ_SCROLLS: case OBJ_POTIONS: // Item is boring only if there's an identical one in inventory. return _item_different_than_inv(item, _identical_types); case OBJ_BOOKS: // Books always start out unidentified. return (true); case OBJ_ORBS: // Orb is always interesting. return (true); default: break; } return (false); } static void _do_autopickup() { bool did_pickup = false; int n_did_pickup = 0; int n_tried_pickup = 0; will_autopickup = false; if (!can_autopickup()) { item_check(false); return; } int o = you.visible_igrd(you.pos()); std::string pickup_warning; while (o != NON_ITEM) { const int next = mitm[o].link; if (item_needs_autopickup(mitm[o])) { int num_to_take = mitm[o].quantity; if (Options.autopickup_no_burden && item_mass(mitm[o]) != 0) { int num_can_take = (carrying_capacity(you.burden_state) - you.burden) / item_mass(mitm[o]); if (num_can_take < num_to_take) { if (!n_tried_pickup) { mpr("You can't pick everything up without burdening " "yourself."); } n_tried_pickup++; num_to_take = num_can_take; } if (num_can_take == 0) { o = next; continue; } } // Do this before it's picked up, otherwise the picked up // item will be in inventory and _interesting_explore_pickup() // will always return false. const bool interesting_pickup = _interesting_explore_pickup(mitm[o]); const unsigned long iflags(mitm[o].flags); mitm[o].flags &= ~(ISFLAG_THROWN | ISFLAG_DROPPED); const int result = move_item_to_player(o, num_to_take); if (result == 0 || result == -1) { n_tried_pickup++; if (result == 0) pickup_warning = "You can't carry any more."; else pickup_warning = "Your pack is full."; mitm[o].flags = iflags; } else { did_pickup = true; if (interesting_pickup) n_did_pickup++; } } o = next; } if (!pickup_warning.empty()) mpr(pickup_warning.c_str()); if (did_pickup) you.turn_is_over = true; item_check(false); explore_pickup_event(n_did_pickup, n_tried_pickup); } void autopickup() { _autoinscribe_floor_items(); _do_autopickup(); } int inv_count(void) { int count = 0; for (int i = 0; i < ENDOFPACK; i++) if (you.inv[i].is_valid()) count++; return count; } item_def *find_floor_item(object_class_type cls, int sub_type) { for (int y = 0; y < GYM; ++y) for (int x = 0; x < GXM; ++x) for (stack_iterator si(coord_def(x,y)); si; ++si) if (si->is_valid() && si->base_type == cls && si->sub_type == sub_type) { return (& (*si)); } return (NULL); } int item_on_floor(const item_def &item, const coord_def& where) { // Check if the item is on the floor and reachable. for (int link = igrd(where); link != NON_ITEM; link = mitm[link].link) if (&mitm[link] == &item) return (link); return (NON_ITEM); } static bool _find_subtype_by_name(item_def &item, object_class_type base_type, int ntypes, const std::string &name) { // In order to get the sub-type, we'll fill out the base type... // then we're going to iterate over all possible subtype values // and see if we get a winner. -- bwr item.base_type = base_type; item.sub_type = OBJ_RANDOM; item.plus = 0; item.plus2 = 0; item.special = 0; item.flags = 0; item.quantity = 1; // Don't use set_ident_flags in order to avoid triggering notes. // FIXME - is this the proper solution? item.flags |= (ISFLAG_KNOW_TYPE | ISFLAG_KNOW_PROPERTIES); int type_wanted = -1; for (int i = 0; i < ntypes; i++) { item.sub_type = i; if (base_type == OBJ_BOOKS && i == BOOK_MANUAL) { for (int j = 0; j < NUM_SKILLS; ++j) { if (!skill_name(j)) continue; item.plus = j; if (name == lowercase_string(item.name(DESC_PLAIN))) { type_wanted = i; i = ntypes; break; } } } else if (name == lowercase_string(item.name(DESC_PLAIN))) { type_wanted = i; break; } } if (type_wanted != -1) item.sub_type = type_wanted; else item.sub_type = OBJ_RANDOM; return (item.sub_type != OBJ_RANDOM); } // Returns an incomplete item_def with base_type and sub_type set correctly // for the given item name. If the name is not found, sets sub_type to // OBJ_RANDOM. item_def find_item_type(object_class_type base_type, std::string name) { item_def item; item.base_type = OBJ_RANDOM; item.sub_type = OBJ_RANDOM; lowercase(name); if (base_type == OBJ_RANDOM || base_type == OBJ_UNASSIGNED) base_type = OBJ_UNASSIGNED; static int max_subtype[] = { NUM_WEAPONS, NUM_MISSILES, NUM_ARMOURS, NUM_WANDS, NUM_FOODS, 0, // unknown I NUM_SCROLLS, NUM_JEWELLERY, NUM_POTIONS, 0, // unknown II NUM_BOOKS, NUM_STAVES, 1, // Orbs -- only one, handled specially NUM_MISCELLANY, 0, // corpses -- handled specially 0, // gold -- handled specially 0, // "gemstones" -- no items of type }; if (base_type == OBJ_UNASSIGNED) { for (unsigned i = 0; i < ARRAYSZ(max_subtype); ++i) { if (max_subtype[i] == 0) continue; if (_find_subtype_by_name(item, static_cast(i), max_subtype[i], name)) { break; } } } else _find_subtype_by_name(item, base_type, max_subtype[base_type], name); return (item); } bool item_is_equipped(const item_def &item, bool quiver_too) { if (!in_inventory(item)) return (false); for (int i = 0; i < NUM_EQUIP; i++) if (item.link == you.equip[i]) return (true); if (quiver_too && item.link == you.m_quiver->get_fire_item()) return (true); return (false); } //////////////////////////////////////////////////////////////////////// // item_def functions. bool item_def::has_spells() const { return ((base_type == OBJ_BOOKS && item_type_known(*this) && sub_type != BOOK_DESTRUCTION && sub_type != BOOK_MANUAL) || count_staff_spells(*this, true) > 1); } int item_def::book_number() const { return (base_type == OBJ_BOOKS ? sub_type : base_type == OBJ_STAVES ? sub_type + NUM_BOOKS - STAFF_FIRST_ROD + 1 : -1); } bool item_def::cursed() const { return (flags & ISFLAG_CURSED); } bool item_def::launched_by(const item_def &launcher) const { if (base_type != OBJ_MISSILES) return (false); const missile_type mt = fires_ammo_type(launcher); return (sub_type == mt || (mt == MI_STONE && sub_type == MI_SLING_BULLET)); } zap_type item_def::zap() const { if (base_type != OBJ_WANDS) return (ZAP_DEBUGGING_RAY); zap_type result = ZAP_DEBUGGING_RAY; switch (static_cast(sub_type)) { case WAND_FLAME: result = ZAP_FLAME; break; case WAND_FROST: result = ZAP_FROST; break; case WAND_SLOWING: result = ZAP_SLOWING; break; case WAND_HASTING: result = ZAP_HASTING; break; case WAND_MAGIC_DARTS: result = ZAP_MAGIC_DARTS; break; case WAND_HEALING: result = ZAP_HEALING; break; case WAND_PARALYSIS: result = ZAP_PARALYSIS; break; case WAND_FIRE: result = ZAP_FIRE; break; case WAND_COLD: result = ZAP_COLD; break; case WAND_CONFUSION: result = ZAP_CONFUSION; break; case WAND_INVISIBILITY: result = ZAP_INVISIBILITY; break; case WAND_DIGGING: result = ZAP_DIGGING; break; case WAND_FIREBALL: result = ZAP_FIREBALL; break; case WAND_TELEPORTATION: result = ZAP_TELEPORTATION; break; case WAND_LIGHTNING: result = ZAP_LIGHTNING; break; case WAND_POLYMORPH_OTHER: result = ZAP_POLYMORPH_OTHER; break; case WAND_ENSLAVEMENT: result = ZAP_ENSLAVEMENT; break; case WAND_DRAINING: result = ZAP_NEGATIVE_ENERGY; break; case WAND_DISINTEGRATION: result = ZAP_DISINTEGRATION; break; case WAND_RANDOM_EFFECTS: result = static_cast(random2(ZAP_LAST_RANDOM + 1)); if (one_chance_in(20)) result = ZAP_NEGATIVE_ENERGY; if (one_chance_in(17)) result = ZAP_ENSLAVEMENT; break; case NUM_WANDS: break; } return (result); } int item_def::index() const { return (this - mitm.buffer()); } int item_def::armour_rating() const { if (!this->is_valid() || base_type != OBJ_ARMOUR) return (0); return (property(*this, PARM_AC) + plus); } monsters* item_def::holding_monster() const { if (!pos.equals(-2, -2)) return (NULL); const int midx = link - NON_ITEM - 1; if (invalid_monster_index(midx)) return (NULL); return (&menv[midx]); } void item_def::set_holding_monster(int midx) { ASSERT(midx != NON_MONSTER); pos.set(-2, -2); link = NON_ITEM + 1 + midx; } bool item_def::held_by_monster() const { return (pos.equals(-2, -2) && !invalid_monster_index(link - NON_ITEM - 1)); } // Note: This function is to isolate all the checks to see if // an item is valid (often just checking the quantity). // // It shouldn't be used a a substitute for those cases // which actually want to check the quantity (as the // rules for unused objects might change). bool item_def::is_valid() const { return (base_type != OBJ_UNASSIGNED && quantity > 0); } // The Orb of Zot and unique runes are considered critical. bool item_def::is_critical() const { if (!is_valid()) return (false); if (base_type == OBJ_ORBS) return (true); return (base_type == OBJ_MISCELLANY && sub_type == MISC_RUNE_OF_ZOT && plus != RUNE_DEMONIC && plus != RUNE_ABYSSAL); } // Is item something that no one would usually bother enchanting? bool item_def::is_mundane() const { switch (base_type) { case OBJ_WEAPONS: if (sub_type == WPN_CLUB || sub_type == WPN_GIANT_CLUB || sub_type == WPN_GIANT_SPIKED_CLUB || sub_type == WPN_KNIFE) { return (true); } break; case OBJ_ARMOUR: if (sub_type == ARM_ANIMAL_SKIN) return (true); break; default: break; } return (false); } static void _rune_from_specs(const char* _specs, item_def &item) { char specs[80]; char obj_name[ ITEMNAME_SIZE ]; item.sub_type = MISC_RUNE_OF_ZOT; if (strstr(_specs, "rune of zot")) strncpy(specs, _specs, strlen(_specs) - strlen(" of zot")); else strcpy(specs, _specs); if (strlen(specs) > 4) { for (int i = 0; i < NUM_RUNE_TYPES; ++i) { item.plus = i; strcpy(obj_name, item.name(DESC_PLAIN).c_str()); if (strstr(strlwr(obj_name), specs)) return; } } while (true) { mpr("[a] iron [b] obsidian [c] icy [d] bone [e] slimy [f] silver", MSGCH_PROMPT); mpr("[g] serpentine [h] elven [i] golden [j] decaying [k] barnacle [l] demonic", MSGCH_PROMPT); mpr("[m] abyssal [n] glowing [o] magical [p] fiery [q] dark [r] buggy", MSGCH_PROMPT); mpr("Which rune (ESC to exit)? ", MSGCH_PROMPT); int keyin = tolower( get_ch() ); if (keyin == ESCAPE || keyin == ' ' || keyin == '\r' || keyin == '\n') { canned_msg( MSG_OK ); item.base_type = OBJ_UNASSIGNED; return; } if (keyin < 'a' || keyin > 'r') continue; rune_type types[] = { RUNE_DIS, RUNE_GEHENNA, RUNE_COCYTUS, RUNE_TARTARUS, RUNE_SLIME_PITS, RUNE_VAULTS, RUNE_SNAKE_PIT, RUNE_ELVEN_HALLS, RUNE_TOMB, RUNE_SWAMP, RUNE_SHOALS, RUNE_DEMONIC, RUNE_ABYSSAL, RUNE_MNOLEG, RUNE_LOM_LOBON, RUNE_CEREBOV, RUNE_GLOORX_VLOQ, NUM_RUNE_TYPES }; item.plus = types[keyin - 'a']; return; } } static void _deck_from_specs(const char* _specs, item_def &item) { std::string specs = _specs; std::string type_str = ""; trim_string(specs); if (specs.find(" of ") != std::string::npos) { type_str = specs.substr(specs.find(" of ") + 4); if (type_str.find("card") != std::string::npos || type_str.find("deck") != std::string::npos) { type_str = ""; } trim_string(type_str); } misc_item_type types[] = { MISC_DECK_OF_ESCAPE, MISC_DECK_OF_DESTRUCTION, MISC_DECK_OF_DUNGEONS, MISC_DECK_OF_SUMMONING, MISC_DECK_OF_WONDERS, MISC_DECK_OF_PUNISHMENT, MISC_DECK_OF_WAR, MISC_DECK_OF_CHANGES, MISC_DECK_OF_DEFENCE, NUM_MISCELLANY }; item.special = DECK_RARITY_COMMON; item.sub_type = NUM_MISCELLANY; if (!type_str.empty()) { for (int i = 0; types[i] != NUM_MISCELLANY; ++i) { item.sub_type = types[i]; item.plus = 1; init_deck(item); // Remove "plain " from front. std::string name = item.name(DESC_PLAIN).substr(6); item.props.clear(); if (name.find(type_str) != std::string::npos) break; } } if (item.sub_type == NUM_MISCELLANY) { while (true) { mpr( "[a] escape [b] destruction [c] dungeons [d] summoning [e] wonders", MSGCH_PROMPT); mpr( "[f] punishment [g] war [h] changes [i] defence", MSGCH_PROMPT); mpr("Which deck (ESC to exit)? "); int keyin = tolower( get_ch() ); if (keyin == ESCAPE || keyin == ' ' || keyin == '\r' || keyin == '\n') { canned_msg( MSG_OK ); item.base_type = OBJ_UNASSIGNED; return; } if (keyin < 'a' || keyin > 'i') continue; item.sub_type = types[keyin - 'a']; break; } } const char* rarities[] = { "plain", "ornate", "legendary", NULL }; int rarity_val = -1; for (int i = 0; rarities[i] != NULL; ++i) if (specs.find(rarities[i]) != std::string::npos) { rarity_val = i; break; } if (rarity_val == -1) { while (true) { mpr("[a] plain [b] ornate [c] legendary? (ESC to exit)", MSGCH_PROMPT); int keyin = tolower( get_ch() ); if (keyin == ESCAPE || keyin == ' ' || keyin == '\r' || keyin == '\n') { canned_msg( MSG_OK ); item.base_type = OBJ_UNASSIGNED; return; } switch (keyin) { case 'p': keyin = 'a'; break; case 'o': keyin = 'b'; break; case 'l': keyin = 'c'; break; } if (keyin < 'a' || keyin > 'c') continue; rarity_val = keyin - 'a'; break; } } const deck_rarity_type rarity = static_cast(DECK_RARITY_COMMON + rarity_val); item.special = rarity; int num = debug_prompt_for_int("How many cards? ", false); if (num <= 0) { canned_msg( MSG_OK ); item.base_type = OBJ_UNASSIGNED; return; } item.plus = num; init_deck(item); } static void _rune_or_deck_from_specs(const char* specs, item_def &item) { if (strstr(specs, "rune")) _rune_from_specs(specs, item); else if (strstr(specs, "deck")) _deck_from_specs(specs, item); } static bool _book_from_spell(const char* specs, item_def &item) { spell_type type = spell_by_name(specs, true); if (type == SPELL_NO_SPELL) return (false); for (int i = 0; i < NUM_FIXED_BOOKS; ++i) for (int j = 0; j < 8; ++j) if (which_spell_in_book(i, j) == type) { item.sub_type = i; return (true); } return (false); } bool get_item_by_name(item_def *item, char* specs, object_class_type class_wanted, bool create_for_real) { static int max_subtype[] = { NUM_WEAPONS, NUM_MISSILES, NUM_ARMOURS, NUM_WANDS, NUM_FOODS, 0, // unknown I NUM_SCROLLS, NUM_JEWELLERY, NUM_POTIONS, 0, // unknown II NUM_BOOKS, NUM_STAVES, 0, // Orbs -- only one, handled specially NUM_MISCELLANY, 0, // corpses -- handled specially 0, // gold -- handled specially 0, // "gemstones" -- no items of type }; char obj_name[ ITEMNAME_SIZE ]; char* ptr; int best_index; int type_wanted = -1; int special_wanted = 0; // In order to get the sub-type, we'll fill out the base type... // then we're going to iterate over all possible subtype values // and see if we get a winner. -- bwr item->base_type = class_wanted; item->sub_type = 0; item->plus = 0; item->plus2 = 0; item->special = 0; item->flags = 0; item->quantity = 1; // Don't use set_ident_flags(), to avoid getting a spurious ID note. item->flags |= ISFLAG_IDENT_MASK; if (class_wanted == OBJ_MISCELLANY) { // Leaves object unmodified if it wasn't a rune or deck. _rune_or_deck_from_specs(specs, *item); if (item->base_type == OBJ_UNASSIGNED) { // Rune or deck creation canceled, clean up item-> return (false); } } if (!item->sub_type) { type_wanted = -1; best_index = 10000; for (int i = 0; i < max_subtype[ item->base_type ]; ++i) { item->sub_type = i; strcpy(obj_name, item->name(DESC_PLAIN).c_str()); ptr = strstr( strlwr(obj_name), specs ); if (ptr != NULL) { // Earliest match is the winner. if (ptr - obj_name < best_index) { if (create_for_real) mpr(obj_name); type_wanted = i; best_index = ptr - obj_name; } } } if (type_wanted != -1) { item->sub_type = type_wanted; if (!create_for_real) return (true); } else { switch (class_wanted) { case OBJ_BOOKS: // Try if we get a match against a spell. if (_book_from_spell(specs, *item)) type_wanted = item->sub_type; break; // Search for a matching unrandart. case OBJ_WEAPONS: case OBJ_ARMOUR: case OBJ_JEWELLERY: { for (int unrand = 0; unrand < NO_UNRANDARTS; ++unrand) { int index = unrand + UNRAND_START; unrandart_entry* entry = get_unrand_entry(index); strcpy(obj_name, entry->name); ptr = strstr( strlwr(obj_name), specs ); if (ptr != NULL && entry->base_type == class_wanted) { make_item_unrandart(*item, index); if (create_for_real) { mprf("%s (%s)", entry->name, debug_art_val_str(*item).c_str()); } return(true); } } // Reset base type to class_wanted, if nothing found. item->base_type = class_wanted; item->sub_type = 0; break; } default: break; } } if (type_wanted == -1) { // ds -- If specs is a valid int, try using that. // Since zero is atoi's copout, the wizard // must enter (subtype + 1). if (!(type_wanted = atoi(specs))) return (false); type_wanted--; item->sub_type = type_wanted; } } if (!create_for_real) return (true); switch (item->base_type) { case OBJ_MISSILES: item->quantity = 30; // intentional fall-through case OBJ_WEAPONS: case OBJ_ARMOUR: { char buf[80]; mpr("What ego type? ", MSGCH_PROMPT); get_input_line( buf, sizeof( buf ) ); if (buf[0] != '\0') { special_wanted = 0; best_index = 10000; for (int i = SPWPN_NORMAL + 1; i < SPWPN_DEBUG_RANDART; ++i) { item->special = i; strcpy(obj_name, item->name(DESC_PLAIN).c_str()); ptr = strstr( strlwr(obj_name), strlwr(buf) ); if (ptr != NULL) { // earliest match is the winner if (ptr - obj_name < best_index) { if (create_for_real) mpr(obj_name); special_wanted = i; best_index = ptr - obj_name; } } } item->special = special_wanted; } break; } case OBJ_BOOKS: if (item->sub_type == BOOK_MANUAL) { special_wanted = debug_prompt_for_skill( "A manual for which skill? " ); if (special_wanted != -1) { item->plus = special_wanted; item->plus2 = 3 + random2(15); } else mpr("Sorry, no books on that skill today."); } else if (type_wanted == BOOK_RANDART_THEME) make_book_theme_randart(*item, 0, 0, 5 + coinflip(), 20); else if (type_wanted == BOOK_RANDART_LEVEL) { int level = random_range(1, 9); int max_spells = 5 + level/3; make_book_level_randart(*item, level, max_spells); } break; case OBJ_WANDS: item->plus = 24; break; case OBJ_STAVES: if (item_is_rod(*item)) { item->plus = MAX_ROD_CHARGE * ROD_CHARGE_MULT; item->plus2 = MAX_ROD_CHARGE * ROD_CHARGE_MULT; } break; case OBJ_MISCELLANY: if (!is_rune(*item) && !is_deck(*item)) item->plus = 50; break; case OBJ_POTIONS: item->quantity = 12; if (is_blood_potion(*item)) { const char* prompt; if (item->sub_type == POT_BLOOD) { prompt = "# turns away from coagulation? " "[ENTER for fully fresh] "; } else { prompt = "# turns away from rotting? " "[ENTER for fully fresh] "; } int age = debug_prompt_for_int(prompt, false); if (age <= 0) age = -1; else if (item->sub_type == POT_BLOOD) age += 500; init_stack_blood_potions(*item, age); } break; case OBJ_FOOD: case OBJ_SCROLLS: item->quantity = 12; break; case OBJ_JEWELLERY: if (jewellery_is_amulet(*item)) break; switch (item->sub_type) { case RING_SLAYING: item->plus2 = 5; // intentional fall-through case RING_PROTECTION: case RING_EVASION: case RING_STRENGTH: case RING_DEXTERITY: case RING_INTELLIGENCE: item->plus = 5; default: break; } default: break; } return (true); }