Automatically update the shopping list if you see the same item for less cost in another shop, or if you get an item identical to one on the shopping list (currently only applies to jewellery, books and staves).
RSVDMFN7LVPYBYCWI3IF3GNIMSSVWVXANOQMH5DN2PGVP3VAOGQAC
OFYW2YRIRSE4TJBFEZ4RROHYVS22T6SJLJXX3FQEBNMWENMBPM4AC
LUSG5HYFRMFNS3RACRCUJNQXJNXPL4SYKHHOIEG5RBQG2DKQXPDAC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
GYRKOLZFYXWJTPEV35USSHCJNA6Y4XMDHSTEZWEBM57WQP2PT6UQC
4DEFHDNO2GUBYL4EGYEAR2IP3KBDXRU7UL7L4P7HEZY2UNR6IYJAC
25EF5X4H3LURXFZ35ZGYGUB6ND7NFQVH4M2XX2SI33I4XRGYG5HAC
Q4TULVRLLNAHZNURPKNAR2K3GBVCEY6KY6ZXKPIJVWVSSZ6R7PEQC
YFIVTYI7PMVAXV23DUPXPAQNEY3YSFIXQGSN32I3WVHMMD5XS5DQC
EFWEYIB2R3DPD3JWIPU6LS6SFLPMYN7J7X4GBZR7DJWKHJ3UELSAC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
BDFIS53HAIHOCXQ5BE7WCO2MEOFCUQPFY4JGUWVLWY6JO3IFMEKQC
U6OTXM3JN7SGPVIGQ5F6NR2I7J5V7KOWFQ7AVNNRQQDNLBEDMYFQC
SZBMBNW34N2SM7Y6QBKBSA7OMLEMLFGCE4NSMHCBH6ORU2MYY2MQC
static std::string name_thing(const CrawlHashTable& thing);
static std::string describe_thing(const CrawlHashTable& thing);
static std::string name_thing(const CrawlHashTable& thing,
description_level_type descrip = DESC_PLAIN);
static std::string describe_thing(const CrawlHashTable& thing,
description_level_type descrip = DESC_PLAIN);
static std::string item_name_simple(const item_def& item,
bool ident = false);
}
static bool _shop_yesno(const char* prompt, int safeanswer)
{
if (_in_shop_now)
{
textcolor(channel_to_colour(MSGCH_PROMPT));
_shop_print(prompt, 1);
return yesno(NULL, true, safeanswer, false, false, true);
}
else
return yesno(prompt, true, safeanswer, false, false, false);
}
static void _shop_mpr(const char* msg)
{
if (_in_shop_now)
{
_shop_print(msg, 1);
_shop_more();
}
else
mpr(msg);
// Cull shopping list after shop contents have been displayed, but
// only once.
if (first_iter)
{
first_iter = false;
unsigned int culled = 0;
for (unsigned int i = 0; i < stock.size(); i++)
{
const item_def& item = mitm[stock[i]];
const int cost = _shop_get_item_value(item, shop.greed,
id_stock);
unsigned int num = shopping_list.cull_identical_items(item,
(long) cost);
if (num > 0)
{
in_list[i] = true;
num_in_list++;
}
culled += num;
}
if (culled > 0)
{
// Some shopping list items have been moved to this store,
// so refresh the display.
mesclr();
continue;
}
}
std::vector<bool> to_buy;
int total_purchase = 0;
if (num_selected == 0 && num_in_list > 0)
{
if (_shop_yesno("Buy items on shopping list? (Y/n)", 'y'))
{
to_buy = in_list;
for (unsigned int i = 0; i < to_buy.size(); i++)
{
if (to_buy[i])
{
const item_def& item = mitm[stock[i]];
total_purchase +=
_shop_get_item_value(item, shop.greed,
id_stock);
}
}
}
}
else
{
to_buy = selected;
total_purchase = total_cost;
}
#define REMOVE_PROMPTED_KEY "remove_prompted_key"
#define REPLACE_PROMPTED_KEY "replace_prompted_key"
// TODO:
//
// * If you get a randart which lets you turn invisible, then remove
// any ordinary rings of invisiblity from the shopping list.
//
// * If you collected enough spellbooks that all the spells in a
// shopping list book are covered, then auto-remove it.
unsigned int ShoppingList::cull_identical_items(const item_def& item,
long cost)
{
// Can't put items in Bazaar shops in the shopping list, so
// don't bother transfering shopping list items to Bazaar shops.
if (cost != -1 && you.level_type != LEVEL_DUNGEON)
return (0);
switch(item.base_type)
{
case OBJ_JEWELLERY:
case OBJ_BOOKS:
case OBJ_STAVES:
// Only these are really interchangable.
break;
default:
return (0);
}
if (!item_type_known(item) || is_artefact(item))
return (0);
// Ignore stat-modification rings which reduce a stat, since they're
// worthless.
if (item.plus < 0 && item.base_type == OBJ_JEWELLERY)
return (0);
// Item is already on shopping-list.
const bool on_list = find_thing(item, level_pos::current()) != -1;
const bool do_prompt =
(item.base_type == OBJ_JEWELLERY && !jewellery_is_amulet(item)
&& ring_has_stackable_effect(item))
// Manuals and tomes of destruction are consumable.
|| (item.base_type == OBJ_BOOKS
&& (item.sub_type == BOOK_MANUAL
|| item.sub_type == BOOK_DESTRUCTION));
bool add_item = false;
std::vector<level_pos> to_del;
// NOTE: Don't modify the shopping list while iterating over it.
for (unsigned int i = 0; i < list->size(); i++)
{
if (_in_shop_now)
mesclr();
CrawlHashTable &thing = (*list)[i];
if (!thing_is_item(thing))
continue;
const item_def& list_item = get_thing_item(thing);
if (list_item.base_type != item.base_type
|| list_item.sub_type != item.sub_type)
{
continue;
}
if (!item_type_known(list_item) || is_artefact(list_item))
continue;
const level_pos list_pos = thing_pos(thing);
// cost = -1, we just found a shop item which is cheaper than
// one on the shopping list.
if (cost != -1)
{
long list_cost = thing_cost(thing);
if (cost >= list_cost)
continue;
// Only prompt once.
if (thing.exists(REPLACE_PROMPTED_KEY))
continue;
thing[REPLACE_PROMPTED_KEY] = (bool) true;
std::string prompt =
make_stringf("Shopping-list: replace %dgp %s with cheaper "
"one? (Y/n)", list_cost,
describe_thing(thing).c_str());
if (_shop_yesno(prompt.c_str(), 'y'))
{
add_item = true;
to_del.push_back(list_pos);
}
continue;
}
// cost == -1, we just got an item which is on the shopping list.
if (do_prompt)
{
// Only prompt once.
if (thing.exists(REMOVE_PROMPTED_KEY))
continue;
thing[REMOVE_PROMPTED_KEY] = (bool) true;
std::string prompt =
make_stringf("Shopping-list: remove %s? (Y/n)",
describe_thing(thing, DESC_NOCAP_A).c_str());
if (_shop_yesno(prompt.c_str(), 'y'))
to_del.push_back(list_pos);
}
else
{
std::string str =
make_stringf("Shopping-list: removing %s",
describe_thing(thing, DESC_NOCAP_A).c_str());
_shop_mpr(str.c_str());
to_del.push_back(list_pos);
}
}
for (unsigned int i = 0; i < to_del.size(); i++)
del_thing(item, &to_del[i]);
if (add_item && !on_list)
add_thing(item, cost);
return (to_del.size());
}
// Item name without plusses, curse-status or inscription.
std::string ShoppingList::item_name_simple(const item_def& item, bool ident)
{
return item.name(DESC_PLAIN, false, ident, false, false,
ISFLAG_KNOW_CURSE | ISFLAG_KNOW_PLUSES);
}
// Returns true if having two rings of the same type on at the same
// has more effect than just having one on.
bool ring_has_stackable_effect( const item_def &item )
{
ASSERT (item.base_type == OBJ_JEWELLERY);
ASSERT (!jewellery_is_amulet(item));
if (!item_type_known(item))
return (false);
if (ring_has_pluses(item))
return (true);
switch (item.sub_type)
{
case RING_PROTECTION_FROM_FIRE:
case RING_PROTECTION_FROM_COLD:
case RING_LIFE_PROTECTION:
case RING_SUSTENANCE:
case RING_WIZARDRY:
case RING_FIRE:
case RING_ICE:
return (true);
default:
break;
}
return (false);
}