diff options
-rw-r--r-- | crawl-ref/source/itemname.cc | 4 | ||||
-rw-r--r-- | crawl-ref/source/itemprop.cc | 37 | ||||
-rw-r--r-- | crawl-ref/source/itemprop.h | 3 | ||||
-rw-r--r-- | crawl-ref/source/items.cc | 2 | ||||
-rw-r--r-- | crawl-ref/source/shopping.cc | 303 | ||||
-rw-r--r-- | crawl-ref/source/shopping.h | 16 |
6 files changed, 327 insertions, 38 deletions
diff --git a/crawl-ref/source/itemname.cc b/crawl-ref/source/itemname.cc index db5f873da3..a0577bad38 100644 --- a/crawl-ref/source/itemname.cc +++ b/crawl-ref/source/itemname.cc @@ -37,6 +37,7 @@ #include "player.h" #include "religion.h" #include "quiver.h" +#include "shopping.h" #include "skills2.h" #include "spl-book.h" #include "state.h" @@ -1813,6 +1814,9 @@ void set_ident_type( item_def &item, item_type_id_state_type setting, item_type_id_state_type old_setting = get_ident_type(item); set_ident_type(item.base_type, item.sub_type, setting, force); + if (in_inventory(item)) + shopping_list.cull_identical_items(item); + if (setting == ID_KNOWN_TYPE && old_setting != ID_KNOWN_TYPE && notes_are_active() && is_interesting_item(item) && !(item.flags & (ISFLAG_NOTED_ID | ISFLAG_NOTED_GET))) diff --git a/crawl-ref/source/itemprop.cc b/crawl-ref/source/itemprop.cc index 04854a0fd0..b271b715ce 100644 --- a/crawl-ref/source/itemprop.cc +++ b/crawl-ref/source/itemprop.cc @@ -32,6 +32,7 @@ #include "player.h" #include "quiver.h" #include "random.h" +#include "shopping.h" #include "stuff.h" #include "transfor.h" #include "xom.h" @@ -617,6 +618,9 @@ void set_ident_flags( item_def &item, unsigned long flags ) { item.flags |= flags; request_autoinscribe(); + + if (in_inventory(item)) + shopping_list.cull_identical_items(item); } if (notes_are_active() && !(item.flags & ISFLAG_NOTED_ID) @@ -2260,7 +2264,7 @@ bool item_is_staff( const item_def &item ) // // Ring functions: -// + // Returns number of pluses on jewellery (always none for amulets yet). int ring_has_pluses( const item_def &item ) { @@ -2289,6 +2293,37 @@ int ring_has_pluses( const item_def &item ) return (0); } +// 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); +} + // // Food functions: // diff --git a/crawl-ref/source/itemprop.h b/crawl-ref/source/itemprop.h index 01379741f5..7a08a9c0d8 100644 --- a/crawl-ref/source/itemprop.h +++ b/crawl-ref/source/itemprop.h @@ -714,7 +714,8 @@ bool item_is_rod( const item_def &item ); bool item_is_staff( const item_def &item ); // ring functions: -int ring_has_pluses( const item_def &item ); +int ring_has_pluses( const item_def &item ); +bool ring_has_stackable_effect( const item_def &item ); // food functions: bool food_is_meat(const item_def &item); diff --git a/crawl-ref/source/items.cc b/crawl-ref/source/items.cc index d23c559507..2bcb48b407 100644 --- a/crawl-ref/source/items.cc +++ b/crawl-ref/source/items.cc @@ -1456,6 +1456,8 @@ int find_free_slot(const item_def &i) static void _got_item(item_def& item, int quant) { + shopping_list.cull_identical_items(item); + if (!is_rune(item)) return; diff --git a/crawl-ref/source/shopping.cc b/crawl-ref/source/shopping.cc index 749cb73143..a73af6b9a8 100644 --- a/crawl-ref/source/shopping.cc +++ b/crawl-ref/source/shopping.cc @@ -38,6 +38,8 @@ ShoppingList shopping_list; +static bool _in_shop_now = false; + static bool _purchase( int shop, int item_got, int cost, bool id); static void _shop_print( const char *shoppy, int line ) @@ -54,6 +56,30 @@ static void _shop_more() get_ch(); } +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); +} + static std::string _hyphenated_suffix(char prev, char last) { std::string s; @@ -114,7 +140,7 @@ static void _list_shop_keys(const std::string &purchasable, bool viewing, pkeys = _purchase_keys(purchasable); std::string shop_list = ""; - if (!viewing) + if (!viewing && you.level_type == LEVEL_DUNGEON) { shop_list = "[<w>@</w>] "; if (num_selected > 0) @@ -274,6 +300,8 @@ static bool _in_a_shop( int shopidx ) { const shop_struct& shop = env.shop[shopidx]; + unwind_bool in_shop(_in_shop_now, true); + cursor_control coff(false); clrscr(); @@ -295,9 +323,11 @@ static bool _in_a_shop( int shopidx ) std::vector<bool> selected; std::vector<bool> in_list; - const bool id_stock = shoptype_identifies_stock(shop.type); + const bool id_stock = shoptype_identifies_stock(shop.type); bool bought_something = false; - bool viewing = false; + bool viewing = false; + bool first_iter = true; + while (true) { ASSERT(total_cost >= 0); @@ -346,6 +376,38 @@ static bool _in_a_shop( int shopidx ) _list_shop_keys(purchasable, viewing, stock.size(), num_selected, num_in_list); + // 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; + } + } + if (!total_cost) { snprintf(info, INFO_SIZE, "You have %d gold piece%s.", you.gold, @@ -407,30 +469,60 @@ static bool _in_a_shop( int shopidx ) break; else if (key == '\r' || key == CK_MOUSE_CLICK) { + 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; + } + // Do purchase. - if (total_cost > you.gold) + if (total_purchase > you.gold) { _shop_print("I'm sorry, you don't seem to have enough money.", 1); } - else if (!total_cost) // Nothing selected. + else if (!total_purchase) // Nothing selected. continue; else { - textcolor(channel_to_colour(MSGCH_PROMPT)); snprintf(info, INFO_SIZE, "Purchase for %d gold? (y/n) ", - total_cost); - _shop_print(info, 1); + total_purchase); - if ( yesno(NULL, true, 'n', false, false, true) ) + if ( _shop_yesno(info, 'n') ) { int num_items = 0, outside_items = 0, quant; - for (int i = selected.size() - 1; i >= 0; --i) + for (int i = to_buy.size() - 1; i >= 0; --i) { - if (selected[i]) + if (to_buy[i]) { item_def& item = mitm[stock[i]]; + // Remove from shopping list. + if (in_list[i]) + shopping_list.del_thing(item); + const int gp_value = _shop_get_item_value(item, shop.greed, id_stock); @@ -454,10 +546,6 @@ static bool _in_a_shop( int shopidx ) // knapsack. outside_items += quant; } - - // Remove from shopping list. - if (in_list[i]) - shopping_list.del_thing(item); } } @@ -483,7 +571,8 @@ static bool _in_a_shop( int shopidx ) browse_inventory(false); else if (key == '@') { - if (viewing || (num_selected == 0 && num_in_list == 0)) + if (viewing || (num_selected == 0 && num_in_list == 0) + || you.level_type != LEVEL_DUNGEON) { _shop_print("Huh?", 1); _shop_more(); @@ -564,10 +653,8 @@ static bool _in_a_shop( int shopidx ) { if (gp_value > you.gold) { - _shop_print("Remove from shopping list? (y/N)", - 1); - - if ( yesno(NULL, true, 'n', false, false, true) ) + if ( _shop_yesno("Remove from shopping list? (y/N)", + 'n') ) { in_list[key] = false; selected[key] = false; @@ -576,10 +663,8 @@ static bool _in_a_shop( int shopidx ) } else { - _shop_print("Remove item from shopping list and buy " - "it? (Y/n)", 1); - - if ( yesno(NULL, true, 'y', false, false, true) ) + if ( _shop_yesno("Remove item from shopping list and " + "buy it? (Y/n)", 'y') ) { in_list[key] = false; // Will be toggled to true later @@ -2041,6 +2126,13 @@ bool ShoppingList::add_thing(const item_def &item, int cost, SETUP_POS(); + if (pos.id.level_type != LEVEL_DUNGEON) + { + mprf("The shopping list can only contain things in the dungeon.", + MSGCH_ERROR); + return (false); + } + if (find_thing(item, pos) != -1) { mprf(MSGCH_ERROR, "%s is already on the shopping list.", @@ -2114,6 +2206,145 @@ bool ShoppingList::del_thing(std::string desc, level_pos* _pos) #undef SETUP_POS +#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()); +} + int ShoppingList::size() const { if (list == NULL) @@ -2159,7 +2390,7 @@ void ShoppingList::gold_changed(int old_amount, int new_amount) if (thing_is_item(thing)) desc = "buy "; - desc += describe_thing(thing); + desc += describe_thing(thing, DESC_NOCAP_A); descs.push_back(desc); } @@ -2234,12 +2465,13 @@ int ShoppingList::find_thing(const item_def &item, for (unsigned int i = 0; i < list->size(); i++) { const CrawlHashTable &thing = (*list)[i]; - const level_pos _pos = thing_pos(thing); + const item_def &_item = get_thing_item(thing); + const level_pos _pos = thing_pos(thing); if (pos != _pos) continue; - if (item.name(DESC_NOCAP_A) == name_thing(thing)) + if (item_name_simple(item) == item_name_simple(_item)) return (i); } @@ -2286,13 +2518,14 @@ level_pos ShoppingList::thing_pos(const CrawlHashTable& thing) return (thing[SHOPPING_THING_POS_KEY].get_level_pos()); } -std::string ShoppingList::name_thing(const CrawlHashTable& thing) +std::string ShoppingList::name_thing(const CrawlHashTable& thing, + description_level_type descrip) { if (thing_is_item(thing)) { const item_def &item = get_thing_item(thing); - return item.name(DESC_NOCAP_A); + return item.name(descrip); } else ASSERT(false); @@ -2300,11 +2533,12 @@ std::string ShoppingList::name_thing(const CrawlHashTable& thing) return (""); } -std::string ShoppingList::describe_thing(const CrawlHashTable& thing) +std::string ShoppingList::describe_thing(const CrawlHashTable& thing, + description_level_type descrip) { const level_pos pos = thing_pos(thing); - std::string desc = name_thing(thing) + " on "; + std::string desc = name_thing(thing, descrip) + " on "; if (pos.id == level_id::current()) desc += "this level"; @@ -2313,3 +2547,10 @@ std::string ShoppingList::describe_thing(const CrawlHashTable& thing) return (desc); } + +// 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); +} diff --git a/crawl-ref/source/shopping.h b/crawl-ref/source/shopping.h index b74d056899..75ed26a2ac 100644 --- a/crawl-ref/source/shopping.h +++ b/crawl-ref/source/shopping.h @@ -46,16 +46,18 @@ public: bool del_thing(const item_def &item, level_pos* pos = NULL); bool del_thing(std::string desc, level_pos* pos = NULL); - int size() const; - - void move_things(const coord_def &src, const coord_def &dst); + unsigned int cull_identical_items(const item_def& item, long cost = -1); void gold_changed(int old_amount, int new_amount); + void move_things(const coord_def &src, const coord_def &dst); + void display_list(); void refresh(); + int size() const; + private: CrawlVector* list; @@ -75,8 +77,12 @@ private: static long thing_cost(const CrawlHashTable& thing); static level_pos thing_pos(const CrawlHashTable& thing); - 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); }; extern ShoppingList shopping_list; |