1 /**
2  * @file
3  * @brief Classes tracking player stashes
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "stash.h"
9 
10 #include <algorithm>
11 #include <cctype>
12 #include <cstdio>
13 #include <sstream>
14 
15 #include "chardump.h"
16 #include "clua.h"
17 #include "cluautil.h"
18 #include "command.h"
19 #include "coordit.h"
20 #include "corpse.h"
21 #include "describe.h"
22 #include "describe-spells.h"
23 #include "directn.h"
24 #include "env.h"
25 #include "files.h"
26 #include "feature.h"
27 #include "god-passive.h"
28 #include "hints.h"
29 #include "invent.h"
30 #include "item-prop.h"
31 #include "item-status-flag-type.h"
32 #include "items.h"
33 #include "libutil.h" // map_find
34 #include "menu.h"
35 #include "message.h"
36 #include "notes.h"
37 #include "output.h"
38 #include "religion.h"
39 #include "spl-book.h"
40 #include "state.h"
41 #include "stringutil.h"
42 #include "syscalls.h"
43 #include "terrain.h"
44 #include "tilepick.h"
45 #include "traps.h"
46 #include "travel.h"
47 #include "unicode.h"
48 #include "unwind.h"
49 #include "viewmap.h"
50 
51 // Global
52 StashTracker StashTrack;
53 
userdef_annotate_item(const char * s,const item_def * item)54 string userdef_annotate_item(const char *s, const item_def *item)
55 {
56     lua_stack_cleaner cleaner(clua);
57     clua_push_item(clua, const_cast<item_def*>(item));
58     if (!clua.callfn(s, 1, 1) && !clua.error.empty())
59         mprf(MSGCH_ERROR, "Lua error: %s", clua.error.c_str());
60     string ann;
61     if (lua_isstring(clua, -1))
62         ann = luaL_checkstring(clua, -1);
63     return ann;
64 }
65 
stash_annotate_item(const char * s,const item_def * item)66 string stash_annotate_item(const char *s, const item_def *item)
67 {
68     // the special-casing of gold here is for the sake of gozag players in
69     // extreme circumstances. It does mean that custom annotation code can't
70     // do anything with gold, but I'm not sure why you'd want to.
71     string text = item->base_type == OBJ_GOLD
72                             ? "{gold}" : userdef_annotate_item(s, item);
73 
74     if (item->has_spells())
75     {
76         formatted_string fs;
77         describe_spellset(item_spellset(*item), item, fs);
78         text += "\n";
79         text += fs.tostring();
80     }
81 
82     // Include singular form (slice of pizza vs slices of pizza).
83     if (item->quantity > 1)
84     {
85         text += " {";
86         text += item->name(DESC_QUALNAME);
87         text += "}";
88     }
89 
90     // note that we can't add this in stash.lua (where most other annotations
91     // are added) because that is shared between stash search annotations and
92     // autopickup configuration annotations, and annotating an item based on
93     // item_needs_autopickup while trying to decide if the item needs to be
94     // autopickedup leads to infinite recursion
95     if (Options.autopickup_search && item_needs_autopickup(*item))
96         text += " {autopickup}";
97 
98     return text;
99 }
100 
maybe_update_stashes()101 void maybe_update_stashes()
102 {
103     if (!crawl_state.game_is_arena())
104         StashTrack.update_visible_stashes();
105 }
106 
is_stash(const coord_def & c)107 bool is_stash(const coord_def& c)
108 {
109     LevelStashes *ls = StashTrack.find_current_level();
110     return ls && ls->find_stash(c);
111 }
112 
get_stash_desc(const coord_def & c)113 string get_stash_desc(const coord_def& c)
114 {
115     LevelStashes *ls = StashTrack.find_current_level();
116     if (ls)
117     {
118         Stash *s = ls->find_stash(c);
119         if (s)
120         {
121             const string desc = s->description();
122             if (!desc.empty())
123                 return "[Stash: " + desc + "]";
124         }
125     }
126     return "";
127 }
128 
describe_stash(const coord_def & c)129 void describe_stash(const coord_def& c)
130 {
131     string desc = get_stash_desc(c);
132     if (!desc.empty())
133         mprf(MSGCH_EXAMINE_FILTER, "%s", desc.c_str());
134 }
135 
get_items() const136 vector<item_def> Stash::get_items() const
137 {
138     return items;
139 }
140 
item_list_in_stash(const coord_def & pos)141 vector<item_def> item_list_in_stash(const coord_def& pos)
142 {
143     vector<item_def> ret;
144 
145     LevelStashes *ls = StashTrack.find_current_level();
146     if (ls)
147     {
148         Stash *s = ls->find_stash(pos);
149         if (s)
150             ret = s->get_items();
151     }
152 
153     return ret;
154 }
155 
_fully_identify_item(item_def * item)156 static void _fully_identify_item(item_def *item)
157 {
158     if (!item || !item->defined())
159         return;
160 
161     set_ident_flags(*item, ISFLAG_IDENT_MASK);
162     if (item->base_type != OBJ_WEAPONS)
163         set_ident_type(*item, true);
164 }
165 
166 // ----------------------------------------------------------------------
167 // Stash
168 // ----------------------------------------------------------------------
169 
Stash(coord_def pos_)170 Stash::Stash(coord_def pos_) : items()
171 {
172     // First, fix what square we're interested in
173     if (pos_.origin())
174         pos_ = you.pos();
175     pos = pos_;
176 
177     update();
178 }
179 
are_items_same(const item_def & a,const item_def & b,bool exact)180 bool Stash::are_items_same(const item_def &a, const item_def &b, bool exact)
181 {
182     const bool same = a.is_type(b.base_type, b.sub_type)
183         // Ignore Gozag's gold flag.
184         && (a.plus == b.plus || a.base_type == OBJ_GOLD && !exact)
185         && a.plus2 == b.plus2
186         && a.special == b.special
187         && a.get_colour() == b.get_colour() // ????????
188         && a.flags == b.flags
189         && a.quantity == b.quantity;
190 
191     return same
192            || (!exact && a.base_type == b.base_type
193                && a.base_type == OBJ_CORPSES
194                && a.plus == b.plus);
195 }
196 
unvisited() const197 bool Stash::unvisited() const
198 {
199     return !visited;
200 }
201 
pickup_eligible() const202 bool Stash::pickup_eligible() const
203 {
204     for (const item_def &item : items)
205         if (item_needs_autopickup(item))
206             return true;
207 
208     return false;
209 }
210 
needs_stop() const211 bool Stash::needs_stop() const
212 {
213     for (const item_def &item : items)
214         if (!item_needs_autopickup(item))
215             return true;
216 
217     return false;
218 }
219 
is_boring_feature(dungeon_feature_type feature)220 bool Stash::is_boring_feature(dungeon_feature_type feature)
221 {
222     // Count shops as boring features, because they are handled separately.
223     return !is_notable_terrain(feature) && !feat_is_trap(feature)
224         || feature == DNGN_ENTER_SHOP;
225 }
226 
_grid_has_perceived_item(const coord_def & pos)227 static bool _grid_has_perceived_item(const coord_def& pos)
228 {
229     return you.visible_igrd(pos) != NON_ITEM;
230 }
231 
unmark_trapping_nets()232 bool Stash::unmark_trapping_nets()
233 {
234     bool changed = false;
235     for (auto &item : items)
236         if (item_is_stationary_net(item))
237             item.net_placed = false, changed = true;
238     return changed;
239 }
240 
update()241 void Stash::update()
242 {
243     feat = env.grid(pos);
244     trap = NUM_TRAPS;
245 
246     if (is_boring_feature(feat))
247         feat = DNGN_FLOOR;
248 
249     if (feat_is_trap(feat))
250     {
251         trap = get_trap_type(pos);
252         if (trap == TRAP_WEB)
253             feat = DNGN_FLOOR, trap = TRAP_UNASSIGNED;
254     }
255 
256     if (feat == DNGN_FLOOR)
257         feat_desc = "";
258     else
259         feat_desc = feature_description_at(pos, false, DESC_A);
260 
261     int previous_size = items.size();
262 
263     // Zap existing items
264     items.clear();
265 
266     if (!_grid_has_perceived_item(pos))
267     {
268         visited = true;
269         return;
270     }
271 
272     // Squares are big, whole piles of loot can be seen on each so
273     // let's update them
274 
275     // There's something on this square. Take a squint at it.
276     item_def *pitem = &env.item[you.visible_igrd(pos)];
277     hints_first_item(*pitem);
278 
279     // Now, grab all items on that square and fill our vector
280     for (stack_iterator si(pos, true); si; ++si)
281     {
282         god_id_item(*si);
283         maybe_identify_base_type(*si);
284         if (!(si->flags & ISFLAG_UNOBTAINABLE))
285             add_item(*si);
286     }
287 
288     visited = pos == you.pos()
289               || static_cast<int>(items.size()) == 1
290               || static_cast<int>(items.size()) == previous_size && visited;
291 }
292 
_is_rottable(const item_def & item)293 static bool _is_rottable(const item_def &item)
294 {
295     if (is_shop_item(item))
296         return false;
297     return item.base_type == OBJ_CORPSES;
298 }
299 
_min_rot(const item_def & item)300 static short _min_rot(const item_def &item)
301 {
302     if (item.is_type(OBJ_CORPSES, CORPSE_SKELETON))
303         return 0;
304 
305     if (!mons_skeleton(item.mon_type))
306         return 0;
307     else
308         return -(FRESHEST_CORPSE);
309 }
310 
311 // Returns the item name for a given item, with any appropriate
312 // stash-tracking pre/suffixes.
stash_item_name(const item_def & item)313 string Stash::stash_item_name(const item_def &item)
314 {
315     string name = item.name(DESC_A);
316 
317     if (in_inventory(item))
318     {
319         name.insert(0, " (carried) ");
320         return name;
321     }
322 
323     if (!_is_rottable(item))
324         return name;
325 
326     if (item.stash_freshness <= _min_rot(item))
327     {
328         name += " (gone by now)";
329         return name;
330     }
331 
332     // Skeletons show no signs of rotting before they're gone
333     if (item.is_type(OBJ_CORPSES, CORPSE_SKELETON))
334         return name;
335 
336     if (item.stash_freshness <= 0)
337         name += " (skeletalised by now)";
338 
339     return name;
340 }
341 
description() const342 string Stash::description() const
343 {
344     if (items.empty())
345         return "";
346 
347     const item_def &item = items[0];
348     string desc = stash_item_name(item);
349 
350     size_t sz = items.size();
351     if (sz > 1)
352     {
353         char additionals[50];
354         snprintf(additionals, sizeof additionals,
355                 " (...%u)",
356                  (unsigned int) (sz - 1));
357         desc += additionals;
358     }
359     return desc;
360 }
361 
feature_description() const362 string Stash::feature_description() const
363 {
364     return feat_desc;
365 }
366 
matches_search(const string & prefix,const base_pattern & search) const367 vector<stash_search_result> Stash::matches_search(
368     const string &prefix, const base_pattern &search) const
369 {
370     vector<stash_search_result> results;
371     if (empty())
372         return results;
373 
374     for (const item_def &item : items)
375     {
376         const string s   = stash_item_name(item);
377         const string ann = stash_annotate_item(STASH_LUA_SEARCH_ANNOTATE, &item);
378         if (search.matches(prefix + " " + ann + " " + s)
379             || is_dumpable_artefact(item) && search.matches(chardump_desc(item)))
380         {
381             stash_search_result res;
382             res.match_type = MATCH_ITEM;
383             res.match = s;
384             res.primary_sort = item.name(DESC_QUALNAME);
385             res.item = item;
386             results.push_back(res);
387         }
388     }
389 
390     if (feat != DNGN_FLOOR)
391     {
392         const string fdesc = feature_description();
393         if (!fdesc.empty() && search.matches(prefix + " " + fdesc))
394         {
395             stash_search_result res;
396             res.match_type = MATCH_FEATURE;
397             res.match = fdesc;
398             res.primary_sort = fdesc;
399             res.feat = feat;
400             res.trap = trap;
401             results.push_back(res);
402         }
403     }
404 
405     for (auto &res : results)
406         res.pos.pos = pos;
407 
408     return results;
409 }
410 
_update_corpses(int rot_time)411 void Stash::_update_corpses(int rot_time)
412 {
413     for (int i = items.size() - 1; i >= 0; i--)
414     {
415         item_def &item = items[i];
416 
417         if (!_is_rottable(item))
418             continue;
419 
420         int new_rot = static_cast<int>(item.stash_freshness) - rot_time;
421 
422         if (new_rot <= _min_rot(item))
423         {
424             items.erase(items.begin() + i);
425             continue;
426         }
427         item.stash_freshness = static_cast<short>(new_rot);
428     }
429 }
430 
_update_identification()431 void Stash::_update_identification()
432 {
433     for (int i = items.size() - 1; i >= 0; i--)
434     {
435         god_id_item(items[i]);
436         maybe_identify_base_type(items[i]);
437     }
438 }
439 
add_item(const item_def & item,bool add_to_front)440 void Stash::add_item(const item_def &item, bool add_to_front)
441 {
442     if (_is_rottable(item))
443         StashTrack.update_corpses();
444 
445     if (add_to_front)
446         items.insert(items.begin(), item);
447     else
448         items.push_back(item);
449 
450     seen_item(item);
451 
452     if (!_is_rottable(item))
453         return;
454 
455     // item.freshness remains unchanged in the stash, to show how fresh it
456     // was when last seen. It's stash_freshness that's decayed over time.
457     item_def &it = add_to_front ? items.front() : items.back();
458     it.stash_freshness     = it.freshness;
459 }
460 
write(FILE * f,coord_def refpos,string place,bool identify) const461 void Stash::write(FILE *f, coord_def refpos, string place, bool identify) const
462 {
463     if (items.empty() && visited)
464         return;
465 
466     no_notes nx;
467 
468     fprintf(f, "(%d, %d%s%s)\n", pos.x - refpos.x, pos.y - refpos.y,
469             place.empty() ? "" : ", ", OUTS(place));
470 
471     for (int i = 0; i < (int) items.size(); ++i)
472     {
473         item_def item = items[i];
474 
475         if (identify)
476             _fully_identify_item(&item);
477 
478         string s = stash_item_name(item);
479 
480         string ann = userdef_annotate_item(STASH_LUA_DUMP_ANNOTATE, &item);
481 
482         if (!ann.empty())
483         {
484             trim_string(ann);
485             ann = " " + ann;
486         }
487 
488         fprintf(f, "  %s%s%s\n", OUTS(s), OUTS(ann),
489             (!visited && (items.size() > 1 || i) ? " (still there?)" : ""));
490 
491         if (is_dumpable_artefact(item))
492         {
493             string desc = chardump_desc(item);
494 
495             // Kill leading and trailing whitespace
496             desc.erase(desc.find_last_not_of(" \n\t") + 1);
497             desc.erase(0, desc.find_first_not_of(" \n\t"));
498             // If string is not-empty, pad out to a neat indent
499             if (!desc.empty())
500             {
501                 // Walk backwards and prepend indenting spaces to \n characters.
502                 for (int j = desc.length() - 1; j >= 0; --j)
503                     if (desc[j] == '\n')
504                         desc.insert(j + 1, " ");
505 
506                 fprintf(f, "    %s\n", OUTS(desc));
507             }
508         }
509     }
510 
511     if (items.size() <= 1 && !visited)
512         fprintf(f, "  (unseen)\n");
513 }
514 
save(writer & outf) const515 void Stash::save(writer& outf) const
516 {
517     // How many items on this square?
518     marshallShort(outf, (short) items.size());
519 
520     marshallByte(outf, pos.x);
521     marshallByte(outf, pos.y);
522 
523     marshallByte(outf, feat);
524     marshallByte(outf, trap);
525 
526     marshallString(outf, feat_desc);
527 
528     marshallByte(outf, visited? 1 : 0);
529 
530     // And dump the items individually. We don't bother saving fields we're
531     // not interested in (and don't anticipate being interested in).
532     for (const item_def &item : items)
533         marshallItem(outf, item, true);
534 }
535 
load(reader & inf)536 void Stash::load(reader& inf)
537 {
538     // How many items?
539     int count = unmarshallShort(inf);
540 
541     pos.x = unmarshallByte(inf);
542     pos.y = unmarshallByte(inf);
543 
544     feat =  static_cast<dungeon_feature_type>(unmarshallUByte(inf));
545     trap =  static_cast<trap_type>(unmarshallUByte(inf));
546     feat_desc = unmarshallString(inf);
547 
548     uint8_t flags = unmarshallUByte(inf);
549     visited = (flags & 1) != 0;
550 
551     // Zap out item vector, in case it's in use (however unlikely)
552     items.clear();
553     // Read in the items
554     for (int i = 0; i < count; ++i)
555     {
556         item_def item;
557         unmarshallItem(inf, item);
558 
559         items.push_back(item);
560     }
561 }
562 
ShopInfo(const shop_struct & shop_)563 ShopInfo::ShopInfo(const shop_struct& shop_)
564     : shop(shop_)
565 {
566 }
567 
shop_item_name(const item_def & it) const568 string ShopInfo::shop_item_name(const item_def &it) const
569 {
570     return make_stringf("%s%s (%d gold)",
571                         Stash::stash_item_name(it).c_str(),
572                         shop_item_unknown(it) ? " (unknown)" : "",
573                         item_price(it, shop));
574 }
575 
shop_item_desc(const item_def & it) const576 string ShopInfo::shop_item_desc(const item_def &it) const
577 {
578     string desc;
579 
580     item_def& item(const_cast<item_def&>(it));
581     unwind_var<iflags_t>(item.flags);
582 
583     if (shoptype_identifies_stock(shop.type))
584         item.flags |= ISFLAG_IDENT_MASK;
585 
586     if (is_dumpable_artefact(item))
587     {
588         desc = chardump_desc(item);
589         trim_string(desc);
590 
591         // Walk backwards and prepend indenting spaces to \n characters
592         for (int i = desc.length() - 1; i >= 0; --i)
593             if (desc[i] == '\n')
594                 desc.insert(i + 1, " ");
595     }
596 
597     return desc;
598 }
599 
show_menu(const level_pos & pos) const600 void ShopInfo::show_menu(const level_pos& pos) const
601 {
602     if (!is_visited())
603         return;
604     // ShopMenu shouldn't actually modify the shop, since it only does so if
605     // you buy something.
606     ::shop(const_cast<shop_struct&>(shop), pos);
607 }
608 
matches_search(const string & prefix,const base_pattern & search) const609 vector<stash_search_result> ShopInfo::matches_search(
610     const string &prefix, const base_pattern &search) const
611 {
612     vector<stash_search_result> results;
613 
614     no_notes nx;
615 
616     const string shoptitle = shop_name(shop) + (shop.stock.empty() ? "*" : "");
617     if (search.matches(shoptitle + " " + prefix + " {shop}"))
618     {
619         stash_search_result res;
620         res.match = shoptitle;
621         res.primary_sort = shoptitle;
622         res.match_type = MATCH_SHOP;
623         res.shop = this;
624         res.pos.pos = shop.pos;
625         results.push_back(res);
626         // if the player is just searching for shops, don't show contents
627         if (search.matches(prefix + " {shop}")
628             && search.tostring() != "." && search.tostring() != "..")
629         {
630             return results;
631         }
632     }
633 
634     for (const item_def &item : shop.stock)
635     {
636         const string sname = shop_item_name(item);
637         const string ann   = stash_annotate_item(STASH_LUA_SEARCH_ANNOTATE,
638                                                  &item);
639 
640         if (search.matches(prefix + " " + ann + " " + sname +
641                                                     " {" + shoptitle + "}")
642             || search.matches(shop_item_desc(item)))
643         {
644             stash_search_result res;
645             res.match_type = MATCH_ITEM;
646             res.match = sname;
647             res.primary_sort = item.name(DESC_QUALNAME);
648             res.item = item;
649             res.pos.pos = shop.pos;
650             results.push_back(res);
651         }
652     }
653 
654     return results;
655 }
656 
write(FILE * f,bool identify) const657 void ShopInfo::write(FILE *f, bool identify) const
658 {
659     no_notes nx;
660     fprintf(f, "[Shop] %s\n", OUTS(shop_name(shop)));
661     if (!shop.stock.empty())
662     {
663         for (item_def item : shop.stock) // intentional copy
664         {
665             if (identify)
666                 _fully_identify_item(&item);
667 
668             fprintf(f, "  %s\n", OUTS(shop_item_name(item)));
669             string desc = shop_item_desc(item);
670             if (!desc.empty())
671                 fprintf(f, "    %s\n", OUTS(desc));
672         }
673     }
674     else
675         fprintf(f, "  (Shop contents are unknown)\n");
676 }
677 
LevelStashes()678 LevelStashes::LevelStashes()
679     : m_place(level_id::current()),
680       m_stashes(),
681       m_shops()
682 {
683 }
684 
where() const685 level_id LevelStashes::where() const
686 {
687     return m_place;
688 }
689 
find_stash(coord_def c)690 Stash *LevelStashes::find_stash(coord_def c)
691 {
692     return map_find(m_stashes, c);
693 }
694 
find_stash(coord_def c) const695 const Stash *LevelStashes::find_stash(coord_def c) const
696 {
697     return map_find(m_stashes, c);
698 }
699 
find_shop(const coord_def & c) const700 const ShopInfo *LevelStashes::find_shop(const coord_def& c) const
701 {
702     for (const ShopInfo &shop : m_shops)
703         if (shop.is_at(c))
704             return &shop;
705 
706     return nullptr;
707 }
708 
shop_needs_visit(const coord_def & c) const709 bool LevelStashes::shop_needs_visit(const coord_def& c) const
710 {
711     const ShopInfo *shop = find_shop(c);
712     return shop && !shop->is_visited();
713 }
714 
needs_visit(const coord_def & c,bool autopickup) const715 bool LevelStashes::needs_visit(const coord_def& c, bool autopickup) const
716 {
717     const Stash *s = find_stash(c);
718     if (s && (s->unvisited()
719               || autopickup && s->pickup_eligible()))
720     {
721         return true;
722     }
723     return shop_needs_visit(c);
724 }
725 
needs_stop(const coord_def & c) const726 bool LevelStashes::needs_stop(const coord_def &c) const
727 {
728     const Stash *s = find_stash(c);
729     return s && s->unvisited() && s->needs_stop();
730 }
731 
get_shop(const coord_def & c)732 ShopInfo &LevelStashes::get_shop(const coord_def& c)
733 {
734     for (ShopInfo &shop : m_shops)
735         if (shop.is_at(c))
736             return shop;
737 
738     shop_struct shop = *shop_at(c);
739     shop.stock.clear(); // You can't see it from afar.
740     m_shops.emplace_back(shop);
741     return m_shops.back();
742 }
743 
744 // Updates the stash at p. Returns true if there was a stash at p, false
745 // otherwise.
update_stash(const coord_def & c)746 bool LevelStashes::update_stash(const coord_def& c)
747 {
748     Stash *s = find_stash(c);
749     if (!s)
750         return false;
751 
752     s->update();
753     if (s->empty())
754         kill_stash(*s);
755     return true;
756 }
757 
unmark_trapping_nets(const coord_def & c)758 bool LevelStashes::unmark_trapping_nets(const coord_def &c)
759 {
760     if (Stash *s = find_stash(c))
761         return s->unmark_trapping_nets();
762     else
763         return false;
764 }
765 
move_stash(const coord_def & from,const coord_def & to)766 void LevelStashes::move_stash(const coord_def& from, const coord_def& to)
767 {
768     ASSERT(from != to);
769 
770     Stash *s = find_stash(from);
771     if (!s)
772         return;
773 
774     coord_def old_pos = s->pos;
775     s->pos = to;
776     m_stashes[s->pos] = *s;
777     m_stashes.erase(old_pos);
778 }
779 
780 // Removes a Stash from the level.
kill_stash(const Stash & s)781 void LevelStashes::kill_stash(const Stash &s)
782 {
783     m_stashes.erase(s.pos);
784 }
785 
add_stash(coord_def p)786 void LevelStashes::add_stash(coord_def p)
787 {
788     Stash *s = find_stash(p);
789     if (s)
790     {
791         s->update();
792         if (s->empty())
793             kill_stash(*s);
794     }
795     else
796     {
797         Stash new_stash(p);
798         if (!new_stash.empty())
799             m_stashes[new_stash.pos] = new_stash;
800     }
801 }
802 
is_current() const803 bool LevelStashes::is_current() const
804 {
805     return m_place == level_id::current();
806 }
807 
level_name() const808 string LevelStashes::level_name() const
809 {
810     return m_place.describe(true, true);
811 }
812 
short_level_name() const813 string LevelStashes::short_level_name() const
814 {
815     return m_place.describe();
816 }
817 
_waypoint_search(int n,vector<stash_search_result> & results) const818 void LevelStashes::_waypoint_search(
819         int n,
820         vector<stash_search_result> &results) const
821 {
822     level_pos waypoint = travel_cache.get_waypoint(n);
823     if (!waypoint.is_valid() || waypoint.id != m_place)
824         return;
825     const Stash* stash = find_stash(waypoint.pos);
826     if (!stash)
827         return;
828     vector<stash_search_result> new_results =
829         stash->matches_search("", text_pattern(".*"));
830     for (auto &res : new_results)
831     {
832         res.pos.id = m_place;
833         results.push_back(res);
834     }
835 }
836 
get_matching_stashes(const base_pattern & search,vector<stash_search_result> & results) const837 void LevelStashes::get_matching_stashes(
838         const base_pattern &search,
839         vector<stash_search_result> &results) const
840 {
841     string lplace = "{" + m_place.describe() + "}";
842 
843     // a single digit or * means we're searching for waypoints' content.
844     const string s = search.tostring();
845     if (s == "*")
846     {
847         for (int i = 0; i < TRAVEL_WAYPOINT_COUNT; ++i)
848             _waypoint_search(i, results);
849         return;
850     }
851     else if (s.size() == 1 && s[0] >= '0' && s[0] <= '9')
852     {
853         _waypoint_search(s[0] - '0', results);
854         return;
855     }
856 
857     for (const auto &entry : m_stashes)
858     {
859         vector<stash_search_result> new_results =
860             entry.second.matches_search(lplace, search);
861         for (auto &res : new_results)
862         {
863             res.pos.id = m_place;
864             results.push_back(res);
865         }
866     }
867 
868     for (const ShopInfo &shop : m_shops)
869     {
870         vector<stash_search_result> new_results =
871             shop.matches_search(lplace, search);
872         for (auto &res : new_results)
873         {
874             res.pos.id = m_place;
875             results.push_back(res);
876         }
877     }
878 }
879 
_update_corpses(int rot_time)880 void LevelStashes::_update_corpses(int rot_time)
881 {
882     for (auto &entry : m_stashes)
883         entry.second._update_corpses(rot_time);
884 }
885 
_update_identification()886 void LevelStashes::_update_identification()
887 {
888     for (auto &entry : m_stashes)
889         entry.second._update_identification();
890 }
891 
write(FILE * f,bool identify) const892 void LevelStashes::write(FILE *f, bool identify) const
893 {
894     if (!has_stashes())
895         return;
896 
897     // very unlikely level names will be localized, but hey
898     fprintf(f, "%s\n", OUTS(level_name()));
899 
900     for (const ShopInfo &shop : m_shops)
901         shop.write(f, identify);
902 
903     if (m_stashes.size())
904     {
905         const Stash &s = m_stashes.begin()->second;
906         string levname = short_level_name();
907         for (const auto &entry : m_stashes)
908             entry.second.write(f, s.pos, levname, identify);
909     }
910     fprintf(f, "\n");
911 }
912 
save(writer & outf) const913 void LevelStashes::save(writer& outf) const
914 {
915     // How many stashes on this level?
916     marshallShort(outf, (short) m_stashes.size());
917 
918     m_place.save(outf);
919 
920     // And write the individual stashes
921     for (const auto &entry : m_stashes)
922         entry.second.save(outf);
923 
924     marshallShort(outf, (short) m_shops.size());
925     for (const ShopInfo &shop : m_shops)
926         shop.save(outf);
927 }
928 
load(reader & inf)929 void LevelStashes::load(reader& inf)
930 {
931     int size = unmarshallShort(inf);
932 
933     m_place.load(inf);
934 
935     m_stashes.clear();
936     for (int i = 0; i < size; ++i)
937     {
938         Stash s;
939         s.load(inf);
940         if (!s.empty())
941             m_stashes[s.pos] = s;
942     }
943 
944     m_shops.clear();
945     int shopc = unmarshallShort(inf);
946     for (int i = 0; i < shopc; ++i)
947     {
948         m_shops.emplace_back();
949         m_shops.back().load(inf);
950     }
951 }
952 
remove_shop(const coord_def & c)953 void LevelStashes::remove_shop(const coord_def& c)
954 {
955     for (unsigned i = 0; i < m_shops.size(); ++i)
956         if (m_shops[i].is_at(c))
957         {
958             m_shops.erase(m_shops.begin() + i);
959             return;
960         }
961 }
962 
get_current_level()963 LevelStashes &StashTracker::get_current_level()
964 {
965     return levels[level_id::current()];
966 }
967 
find_level(const level_id & id)968 LevelStashes *StashTracker::find_level(const level_id &id)
969 {
970     return map_find(levels, id);
971 }
972 
find_current_level()973 LevelStashes *StashTracker::find_current_level()
974 {
975     return find_level(level_id::current());
976 }
977 
update_stash(const coord_def & c)978 bool StashTracker::update_stash(const coord_def& c)
979 {
980     LevelStashes *lev = find_current_level();
981     if (lev)
982     {
983         bool res = lev->update_stash(c);
984         if (!lev->has_stashes())
985             remove_level();
986         return res;
987     }
988     return false;
989 }
990 
move_stash(const coord_def & from,const coord_def & to)991 void StashTracker::move_stash(const coord_def& from, const coord_def& to)
992 {
993     if (LevelStashes *lev = find_current_level())
994         lev->move_stash(from, to);
995 }
996 
unmark_trapping_nets(const coord_def & c)997 bool StashTracker::unmark_trapping_nets(const coord_def &c)
998 {
999     if (LevelStashes *lev = find_current_level())
1000         return lev->unmark_trapping_nets(c);
1001     else
1002         return false;
1003 }
1004 
remove_level(const level_id & place)1005 void StashTracker::remove_level(const level_id &place)
1006 {
1007     levels.erase(place);
1008 }
1009 
add_stash(coord_def p)1010 void StashTracker::add_stash(coord_def p)
1011 {
1012     LevelStashes &current = get_current_level();
1013     current.add_stash(p);
1014 
1015     if (!current.has_stashes())
1016         remove_level();
1017 }
1018 
dump(const char * filename,bool identify) const1019 void StashTracker::dump(const char *filename, bool identify) const
1020 {
1021     FILE *outf = fopen_u(filename, "w");
1022     if (outf)
1023     {
1024         write(outf, identify);
1025         fclose(outf);
1026     }
1027 }
1028 
write(FILE * f,bool identify) const1029 void StashTracker::write(FILE *f, bool identify) const
1030 {
1031     fprintf(f, "%s\n\n", OUTS(you.your_name));
1032     if (!levels.size())
1033         fprintf(f, "  You have no stashes.\n");
1034     else
1035     {
1036         for (const auto &entry : levels)
1037             entry.second.write(f, identify);
1038     }
1039 }
1040 
save(writer & outf) const1041 void StashTracker::save(writer& outf) const
1042 {
1043     // Time of last corpse update.
1044     marshallInt(outf, last_corpse_update);
1045 
1046     // How many levels have we?
1047     marshallShort(outf, (short) levels.size());
1048 
1049     // And ask each level to write itself to the tag
1050     for (const auto &entry : levels)
1051         entry.second.save(outf);
1052 }
1053 
load(reader & inf)1054 void StashTracker::load(reader& inf)
1055 {
1056     // Time of last corpse update.
1057     last_corpse_update = unmarshallInt(inf);
1058 
1059     int count = unmarshallShort(inf);
1060 
1061     levels.clear();
1062     for (int i = 0; i < count; ++i)
1063     {
1064         LevelStashes st;
1065         st.load(inf);
1066         if (st.has_stashes())
1067             levels[st.where()] = st;
1068     }
1069 }
1070 
update_visible_stashes()1071 void StashTracker::update_visible_stashes()
1072 {
1073     LevelStashes *lev = find_current_level();
1074     for (vision_iterator ri(you); ri; ++ri)
1075     {
1076         const dungeon_feature_type feat = env.grid(*ri);
1077 
1078         if ((!lev || !lev->update_stash(*ri))
1079             && (_grid_has_perceived_item(*ri)
1080                 || !Stash::is_boring_feature(feat)))
1081         {
1082             if (!lev)
1083                 lev = &get_current_level();
1084             lev->add_stash(*ri);
1085         }
1086 
1087         if (feat == DNGN_ENTER_SHOP)
1088             get_shop(*ri);
1089     }
1090 
1091     if (lev && !lev->has_stashes())
1092         remove_level();
1093 }
1094 
1095 #define SEARCH_SPAM_THRESHOLD 400
1096 static string lastsearch;
1097 static input_history search_history(15);
1098 
stash_search_prompt()1099 string StashTracker::stash_search_prompt()
1100 {
1101     vector<string> opts;
1102     if (!lastsearch.empty())
1103     {
1104         const string disp = replace_all(lastsearch, "<", "<<");
1105         opts.push_back(
1106             make_stringf("Enter for \"%s\"", disp.c_str()));
1107     }
1108     if (lastsearch != ".")
1109         opts.emplace_back("? for help");
1110 
1111     string prompt_qual =
1112         comma_separated_line(opts.begin(), opts.end(), ", or ", ", or ");
1113 
1114     if (!prompt_qual.empty())
1115         prompt_qual = " [" + prompt_qual + "]";
1116 
1117     return make_stringf("Search for what%s? ", prompt_qual.c_str());
1118 }
1119 
remove_shop(const level_pos & pos)1120 void StashTracker::remove_shop(const level_pos &pos)
1121 {
1122     LevelStashes *lev = find_level(pos.id);
1123     if (lev)
1124         lev->remove_shop(pos.pos);
1125 }
1126 
1127 class stash_search_reader : public line_reader
1128 {
1129 public:
stash_search_reader(char * buf,size_t sz,int wcol=get_number_of_cols ())1130     stash_search_reader(char *buf, size_t sz,
1131                         int wcol = get_number_of_cols())
1132         : line_reader(buf, sz, wcol)
1133     {
1134         set_input_history(&search_history);
1135 #ifdef USE_TILE_WEB
1136         tag = "stash_search";
1137 #endif
1138     }
1139 protected:
process_key(int ch)1140     int process_key(int ch) override
1141     {
1142         if (ch == '?' && !pos)
1143         {
1144             *buffer = 0;
1145             return ch;
1146         }
1147         return line_reader::process_key(ch);
1148     }
1149 };
1150 
_is_potentially_boring(stash_search_result res)1151 static bool _is_potentially_boring(stash_search_result res)
1152 {
1153     return res.match_type == MATCH_ITEM && res.item.defined()
1154         && !res.in_inventory
1155         && (res.item.base_type == OBJ_WEAPONS
1156             || res.item.base_type == OBJ_ARMOUR
1157             || res.item.base_type == OBJ_MISSILES)
1158         && (item_type_known(res.item) || !item_is_branded(res.item))
1159         || res.match_type == MATCH_FEATURE && feat_is_trap(res.feat);
1160 }
1161 
_is_duplicate_for_search(stash_search_result l,stash_search_result r,bool ignore_missile_stacks=true)1162 static bool _is_duplicate_for_search(stash_search_result l,
1163                                      stash_search_result r,
1164                                      bool ignore_missile_stacks=true)
1165 {
1166     if (l.in_inventory || r.in_inventory)
1167         return false;
1168     if (ignore_missile_stacks &&
1169         l.item.base_type == OBJ_MISSILES
1170         && r.item.base_type == OBJ_MISSILES
1171         && l.item.sub_type == r.item.sub_type
1172         && l.item.brand == r.item.brand
1173         && is_shop_item(l.item) == is_shop_item(r.item))
1174     {
1175         // Special handling for missile deduplication: ignore that stacks
1176         // of different sizes have different "names".
1177         return true;
1178     }
1179     // Otherwise just use the search result description.
1180     // TODO: better handling for items in shops (ideally, ignore price)
1181     return l.match == r.match;
1182 }
1183 
1184 
1185 // helper for search_stashes
_compare_by_distance(const stash_search_result & lhs,const stash_search_result & rhs)1186 static bool _compare_by_distance(const stash_search_result& lhs,
1187                                  const stash_search_result& rhs)
1188 {
1189     if (lhs.player_distance != rhs.player_distance)
1190     {
1191         // Sort by increasing distance
1192         return lhs.player_distance < rhs.player_distance;
1193     }
1194     else if (lhs.player_distance == 0)
1195     {
1196         // If on the same level, sort by distance to player.
1197         const int lhs_dist = grid_distance(you.pos(), lhs.pos.pos);
1198         const int rhs_dist = grid_distance(you.pos(), rhs.pos.pos);
1199         if (lhs_dist != rhs_dist)
1200             return lhs_dist < rhs_dist;
1201     }
1202 
1203     if (lhs.match != rhs.match)
1204     {
1205         // Then by name.
1206         return lhs.match < rhs.match;
1207     }
1208     else
1209         return false;
1210 }
1211 
1212 // helper for search_stashes
_compare_by_name(const stash_search_result & lhs,const stash_search_result & rhs)1213 static bool _compare_by_name(const stash_search_result& lhs,
1214                              const stash_search_result& rhs)
1215 {
1216     if (lhs.primary_sort != rhs.primary_sort)
1217     {
1218         // Sort first by DESC_QUALNAME for items
1219         return lhs.primary_sort < rhs.primary_sort;
1220     }
1221     else if (!_is_duplicate_for_search(lhs, rhs, true))
1222         // are the matches not equal for deduplication purposes?
1223     {
1224         // Then sort by 1. whether the item is in a shop, and 2. the full
1225         // stash description (which is DESC_A plus other stuff). The shop
1226         // check is there so that non-shop ammo (which isn't considered a
1227         // search duplicate for shop ammo) will be adjacent, and thus
1228         // collapsible, in _stash_filter_duplicates.
1229         const bool l_shop = is_shop_item(lhs.item);
1230         const bool r_shop = is_shop_item(rhs.item);
1231 
1232         return !l_shop && r_shop
1233             || l_shop == r_shop && lhs.match < rhs.match;
1234     }
1235     else if (lhs.player_distance != rhs.player_distance)
1236     {
1237         // Then sort by increasing distance
1238         return lhs.player_distance < rhs.player_distance;
1239     }
1240     else
1241     {
1242         // If on the same level, sort by distance to player.
1243         const int lhs_dist = grid_distance(you.pos(), lhs.pos.pos);
1244         const int rhs_dist = grid_distance(you.pos(), rhs.pos.pos);
1245         return lhs_dist < rhs_dist;
1246     }
1247 }
1248 
_inventory_search(const base_pattern & search)1249 static vector<stash_search_result> _inventory_search(const base_pattern &search)
1250 {
1251     vector<stash_search_result> results;
1252     for (const item_def &item : you.inv)
1253     {
1254         if (!item.defined())
1255             continue;
1256 
1257         const string s   = Stash::stash_item_name(item);
1258         const string ann = stash_annotate_item(STASH_LUA_SEARCH_ANNOTATE, &item);
1259         if (search.matches(ann + " " + s)
1260             || is_dumpable_artefact(item)
1261                && search.matches(chardump_desc(item)))
1262         {
1263             stash_search_result res;
1264             res.match = s;
1265             res.primary_sort = s; // don't use DESC_QUALNAME for inventory items
1266             res.item = item;
1267             // Needs to not be equal to ITEM_IN_INVENTORY so the describe
1268             // menu doesn't think it can manipulate the item.
1269             res.item.pos = you.pos();
1270             res.in_inventory = true;
1271             res.pos = level_pos::current();
1272             results.push_back(res);
1273         }
1274     }
1275 
1276     return results;
1277 }
1278 
1279 /*
1280  * Eliminate boring duplicates from stash search results. Uses `match`
1281  * to determine whether something is a duplicate.
1282  *
1283  * Populates the `duplicates` field of the search results as a side effect.
1284  *
1285  * @param in  the search result to filter.
1286  * @return a vector sorted by `match`.
1287  */
_stash_filter_duplicates(vector<stash_search_result> & in)1288 static vector<stash_search_result> _stash_filter_duplicates(vector<stash_search_result> &in)
1289 {
1290     vector<stash_search_result> out;
1291     out.clear();
1292     out.reserve(in.size());
1293     // TODO: any problems doing this in place?
1294     // Everything gets resorted before display.
1295     stable_sort(in.begin(), in.end(), _compare_by_name);
1296 
1297     for (const stash_search_result &res : in)
1298     {
1299         if (out.size() && !out.back().in_inventory &&
1300             _is_potentially_boring(res) && _is_duplicate_for_search(out.back(),
1301                                                                     res))
1302         // don't push_back duplicates
1303         {
1304             stash_match_type mtype = out.back().match_type;
1305             switch (mtype)
1306             {
1307             case MATCH_ITEM:
1308                 out.back().duplicate_piles++;
1309                 out.back().duplicates += res.item.quantity;
1310                 break;
1311             case MATCH_FEATURE:
1312                 // number of piles is meaningless for features; just keep track
1313                 // of how many we've found
1314                 out.back().duplicates++;
1315                 break;
1316                 // We shouldn't get here (shops aren't boring enough to
1317                 // deduplicate). But in case it becomes possible we won't
1318                 // collapse entries.
1319             default:
1320                 out.push_back(res);
1321                 out.back().duplicate_piles = 0;
1322                 out.back().duplicates = 0;
1323                 break;
1324             }
1325         }
1326         else
1327         {
1328             out.push_back(res);
1329             out.back().duplicate_piles = 0;
1330             out.back().duplicates = 0;
1331         }
1332     }
1333     return out;
1334 }
1335 
search_stashes(string search_term)1336 void StashTracker::search_stashes(string search_term)
1337 {
1338     char buf[400];
1339 
1340     update_corpses();
1341     update_identification();
1342 
1343     if (search_term.empty())
1344     {
1345         stash_search_reader reader(buf, sizeof buf);
1346 
1347         bool validline = false;
1348         msgwin_prompt(stash_search_prompt());
1349         while (true)
1350         {
1351             int ret = reader.read_line();
1352             if (!ret)
1353             {
1354                 validline = true;
1355                 break;
1356             }
1357             else if (ret == '?')
1358             {
1359                 show_stash_search_help();
1360                 redraw_screen();
1361                 update_screen();
1362             }
1363             else
1364                 break;
1365         }
1366         msgwin_reply(validline ? buf : "");
1367 
1368         clear_messages();
1369         if (!validline || (!*buf && lastsearch.empty()))
1370         {
1371             canned_msg(MSG_OK);
1372             return;
1373         }
1374     }
1375     string csearch_literal = search_term.empty() ? (*buf? buf : lastsearch) : search_term;
1376     string csearch = csearch_literal;
1377 
1378     bool curr_lev = (csearch[0] == '@' || csearch == ".");
1379     if (curr_lev)
1380     {
1381         csearch.erase(0, 1);
1382         if (csearch.length() == 0)
1383             csearch = ".";
1384     }
1385 
1386     base_pattern *search = nullptr;
1387 
1388     lua_text_pattern ltpat(csearch);
1389     text_pattern tpat(csearch, true);
1390     plaintext_pattern ptpat(csearch, true);
1391 
1392     if (lua_text_pattern::is_lua_pattern(csearch))
1393         search = &ltpat;
1394     else if (csearch[0] != '='
1395              && (csearch == "." || csearch == ".." || csearch[0] == '/'
1396                  || Options.regex_search))
1397     {
1398         if (csearch[0] == '/')
1399             csearch.erase(0, 1);
1400         tpat = csearch;
1401         search = &tpat;
1402     }
1403     else
1404     {
1405         if (csearch[0] == '=')
1406             csearch.erase(0, 1);
1407         ptpat = csearch;
1408         search = &ptpat;
1409     }
1410 
1411     if (!search->valid() && csearch != "*")
1412     {
1413         mprf(MSGCH_PLAIN, "Your search expression is invalid.");
1414         return ;
1415     }
1416 
1417     lastsearch = csearch_literal;
1418 
1419     vector<stash_search_result> results;
1420     if (!curr_lev)
1421         results = _inventory_search(*search);
1422     get_matching_stashes(*search, results, curr_lev);
1423 
1424     if (results.empty())
1425     {
1426         mprf(MSGCH_PLAIN, "Can't find anything matching that.");
1427         return;
1428     }
1429 
1430     // The spam threshold works a lot better if we use the deduplicated size.
1431     vector<stash_search_result> dedup_results = _stash_filter_duplicates(results);
1432 
1433     if (dedup_results.size() > SEARCH_SPAM_THRESHOLD)
1434     {
1435         mprf(MSGCH_PLAIN, "Too many matches; use a more specific search.");
1436         return;
1437     }
1438 
1439     dedup_results.erase(remove_if(dedup_results.begin(), dedup_results.end(),
1440         [](const stash_search_result res) {
1441             return res.item.defined() && is_useless_item(res.item, false);
1442         }), dedup_results.end());
1443 
1444     bool sort_by_dist = true;
1445     bool filter_useless = true;
1446     bool default_execute = true;
1447     while (true)
1448     {
1449         bool again;
1450         // Note that sort_by_dist and filter_useless can be modified by the
1451         // following call if requested by the user. Also, "results" will be
1452         // sorted by the call as appropriate:
1453         if (filter_useless)
1454         {
1455             // use the deduplicated results if we are filtering useless items
1456             again = display_search_results(dedup_results,
1457                                            sort_by_dist,
1458                                            filter_useless,
1459                                            default_execute,
1460                                            search,
1461                                            csearch == "."
1462                                            || csearch == "..",
1463                                            results.size());
1464         }
1465         else
1466         {
1467             again = display_search_results(results,
1468                                            sort_by_dist,
1469                                            filter_useless,
1470                                            default_execute,
1471                                            search,
1472                                            csearch == "."
1473                                            || csearch == "..",
1474                                            dedup_results.size());
1475         }
1476         if (!again)
1477             break;
1478     }
1479 }
1480 
get_matching_stashes(const base_pattern & search,vector<stash_search_result> & results,bool curr_lev) const1481 void StashTracker::get_matching_stashes(
1482         const base_pattern &search,
1483         vector<stash_search_result> &results,
1484         bool curr_lev)
1485     const
1486 {
1487     level_id curr = level_id::current();
1488     for (const auto &entry : levels)
1489     {
1490         if (curr_lev && curr != entry.first)
1491             continue;
1492         entry.second.get_matching_stashes(search, results);
1493     }
1494 
1495     for (stash_search_result &result : results)
1496     {
1497         int ldist = level_distance(curr, result.pos.id);
1498         if (ldist == -1)
1499             ldist = 1000;
1500 
1501         result.player_distance = ldist;
1502     }
1503 }
1504 
1505 class StashSearchMenu : public Menu
1506 {
1507 public:
StashSearchMenu(const char * sort_style_,const char * filtered_)1508     StashSearchMenu(const char* sort_style_,const char* filtered_)
1509         : Menu(MF_MULTISELECT | MF_ALLOW_FORMATTING),
1510           request_toggle_sort_method(false),
1511           request_toggle_filter_useless(false),
1512           sort_style(sort_style_),
1513           filtered(filtered_)
1514     { }
1515 
1516 public:
1517     bool request_toggle_sort_method;
1518     bool request_toggle_filter_useless;
1519     const char* sort_style;
1520     const char* filtered;
1521 
1522 protected:
1523     bool process_key(int key) override;
1524     virtual formatted_string calc_title() override;
1525 };
1526 
calc_title()1527 formatted_string StashSearchMenu::calc_title()
1528 {
1529     const int num_matches = items.size();
1530     const int num_alt_matches = title->quantity;
1531     formatted_string fs;
1532     fs.textcolour(title->colour);
1533     string prefixes[] = {
1534         make_stringf("%d match%s",
1535             num_alt_matches, num_alt_matches == 1 ? "" : "es"),
1536         make_stringf("%d match%s",
1537             num_matches, num_matches == 1 ? "" : "es"),
1538     };
1539     const bool f = num_matches != num_alt_matches;
1540     fs.cprintf(prefixes[f]);
1541     if (num_matches == 0 && filtered)
1542     {
1543         // TODO: it might be better to just force filtered=false in the
1544         // display loop if only useless items are found.
1545         fs += formatted_string::parse_string(
1546             "<lightgrey>"
1547             ": only useless items found; press <w>=</w> to show."
1548             "                    "
1549             "</lightgrey>");
1550     }
1551     else
1552     {
1553         fs += formatted_string::parse_string(make_stringf(
1554             "<lightgrey>"
1555             ": <w>%s</w> [toggle: <w>!</w>],"
1556             " by <w>%s</w> [<w>/</w>],"
1557             " <w>%s</w> useless & duplicates [<w>=</w>]"
1558             "</lightgrey>",
1559             menu_action == ACT_EXECUTE ? "travel" : "view  ",
1560             sort_style, filtered));
1561     }
1562     fs.cprintf(string(max(0, strwidth(prefixes[!f])-strwidth(prefixes[f])),
1563                       ' '));
1564     return fs;
1565 }
1566 
process_key(int key)1567 bool StashSearchMenu::process_key(int key)
1568 {
1569     if (key == '/')
1570     {
1571         request_toggle_sort_method = true;
1572         return false;
1573     }
1574     else if (key == '=')
1575     {
1576         request_toggle_filter_useless = true;
1577         return false;
1578     }
1579 
1580     return Menu::process_key(key);
1581 }
1582 
1583 // Returns true to request redisplay if display method was toggled
display_search_results(vector<stash_search_result> & results_in,bool & sort_by_dist,bool & filter_useless,bool & default_execute,base_pattern * search,bool nohl,size_t num_alt_results)1584 bool StashTracker::display_search_results(
1585     vector<stash_search_result> &results_in,
1586     bool& sort_by_dist,
1587     bool& filter_useless,
1588     bool& default_execute,
1589     base_pattern* search,
1590     bool nohl,
1591     size_t num_alt_results)
1592 {
1593     vector<stash_search_result> * results = &results_in;
1594 
1595     if (sort_by_dist)
1596         stable_sort(results->begin(), results->end(), _compare_by_distance);
1597     else
1598         stable_sort(results->begin(), results->end(), _compare_by_name);
1599 
1600     StashSearchMenu stashmenu(sort_by_dist ? "dist" : "name",
1601                               filter_useless ? "hide" : "show");
1602     stashmenu.set_tag("stash");
1603     stashmenu.action_cycle = Menu::CYCLE_TOGGLE;
1604     stashmenu.menu_action  = default_execute ? Menu::ACT_EXECUTE
1605                                              : Menu::ACT_EXAMINE;
1606     string title = "match";
1607 
1608     MenuEntry *mtitle = new MenuEntry(title, MEL_TITLE);
1609     // Abuse of the quantity field.
1610     mtitle->quantity = num_alt_results;
1611     stashmenu.set_title(mtitle);
1612 
1613     menu_letter hotkey;
1614     for (stash_search_result &res : *results)
1615     {
1616         ostringstream matchtitle;
1617         if (!res.in_inventory)
1618         {
1619             if (const uint8_t waypoint = travel_cache.is_waypoint(res.pos))
1620                 matchtitle << "(" << waypoint << ") ";
1621             matchtitle << "[" << res.pos.id.describe() << "] ";
1622         }
1623 
1624         matchtitle << res.match;
1625         if (res.duplicates > 0)
1626         {
1627             matchtitle << " (" << res.duplicates << " further duplicate" <<
1628                 (res.duplicates == 1 ? "" : "s");
1629             if (res.duplicates != res.duplicate_piles  // piles are only
1630                                                        // meaningful for items
1631                 && res.match_type == MATCH_ITEM)
1632             {
1633                 matchtitle << " in " << res.duplicate_piles
1634                            << " pile" << (res.duplicate_piles == 1 ? "" : "s");
1635             }
1636             matchtitle << ")";
1637         }
1638 
1639         MenuEntry *me = new MenuEntry(matchtitle.str(), MEL_ITEM, 1,
1640                                       res.in_inventory ? 0
1641                                                        : (int)hotkey);
1642         me->data = &res;
1643 
1644         if (res.shop && !res.shop->is_visited())
1645             me->colour = CYAN;
1646 
1647         if (res.item.defined())
1648         {
1649             const int itemcol = menu_colour(res.item.name(DESC_PLAIN).c_str(),
1650                                             item_prefix(res.item), "pickup");
1651             if (itemcol != -1)
1652                 me->colour = itemcol;
1653         }
1654 
1655         if (res.item.defined())
1656         {
1657             vector<tile_def> item_tiles;
1658             get_tiles_for_item(res.item, item_tiles, false);
1659             for (const auto &tile : item_tiles)
1660                 me->add_tile(tile);
1661         }
1662         else if (res.shop)
1663             me->add_tile(tile_def(tileidx_shop(&res.shop->shop)));
1664         else if (feat_is_trap(res.feat))
1665             me->add_tile(tile_def(tileidx_trap(res.trap)));
1666         else if (feat_is_runed(res.feat))
1667         {
1668             // Handle large doors and huge gates
1669             me->add_tile(tile_def(tileidx_feature_base(res.feat)));
1670         }
1671         else
1672         {
1673             const dungeon_feature_type feat = feat_by_desc(res.match);
1674             me->add_tile(tile_def(tileidx_feature_base(feat)));
1675         }
1676 
1677         stashmenu.add_entry(me);
1678         if (!res.in_inventory)
1679             ++hotkey;
1680     }
1681 
1682     stashmenu.set_flags(MF_SINGLESELECT | MF_ALLOW_FORMATTING);
1683 
1684     stashmenu.on_single_selection = [&stashmenu, &search, &nohl](const MenuEntry& item)
1685     {
1686         stash_search_result *res = static_cast<stash_search_result *>(item.data);
1687         if (stashmenu.menu_action == StashSearchMenu::ACT_EXAMINE)
1688         {
1689             if (res->item.defined())
1690             {
1691                 item_def it = res->item;
1692                 describe_item_popup(it,
1693                     [search, nohl](string& desc)
1694                     {
1695                         if (!nohl)
1696                             desc = search->match_location(desc).annotate_string("lightcyan");
1697                     });
1698             }
1699             else if (res->shop)
1700                 res->shop->show_menu(res->pos);
1701             else
1702             {
1703                 level_excursion le;
1704                 le.go_to(res->pos.id);
1705                 describe_feature_wide(res->pos.pos);
1706             }
1707         }
1708         else
1709         {
1710             level_pos lp = res->pos;
1711             if (show_map(lp, true, true))
1712             {
1713                 start_translevel_travel(lp);
1714                 return false;
1715             }
1716         }
1717         return true;
1718     };
1719 
1720     vector<MenuEntry*> sel = stashmenu.show();
1721     redraw_screen();
1722     update_screen();
1723     default_execute = stashmenu.menu_action == Menu::ACT_EXECUTE;
1724     if (stashmenu.request_toggle_sort_method)
1725     {
1726         sort_by_dist = !sort_by_dist;
1727         return true;
1728     }
1729     if (stashmenu.request_toggle_filter_useless)
1730     {
1731         filter_useless = !filter_useless;
1732         return true;
1733     }
1734     return false;
1735 }
1736 
update_corpses()1737 void StashTracker::update_corpses()
1738 {
1739     if (you.elapsed_time - last_corpse_update < ROT_TIME_FACTOR)
1740         return;
1741 
1742     const int rot_time =
1743         (you.elapsed_time - last_corpse_update) / ROT_TIME_FACTOR;
1744 
1745     last_corpse_update = you.elapsed_time;
1746 
1747     for (auto &entry : levels)
1748         entry.second._update_corpses(rot_time);
1749 }
1750 
update_identification()1751 void StashTracker::update_identification()
1752 {
1753     if (!have_passive(passive_t::identify_items))
1754         return;
1755 
1756     for (auto &entry : levels)
1757         entry.second._update_identification();
1758 }
1759 
1760 //////////////////////////////////////////////
1761 
ST_ItemIterator()1762 ST_ItemIterator::ST_ItemIterator()
1763 {
1764     m_stash_level_it = StashTrack.levels.begin();
1765     new_level();
1766     //(*this)++;
1767 }
1768 
operator bool() const1769 ST_ItemIterator::operator bool() const
1770 {
1771     return m_item != nullptr;
1772 }
1773 
operator *() const1774 const item_def& ST_ItemIterator::operator *() const
1775 {
1776     return *m_item;
1777 }
1778 
operator ->() const1779 const item_def* ST_ItemIterator::operator->() const
1780 {
1781     return m_item;
1782 }
1783 
place()1784 const level_id &ST_ItemIterator::place()
1785 {
1786     return m_place;
1787 }
1788 
shop()1789 const ShopInfo* ST_ItemIterator::shop()
1790 {
1791     return m_shop;
1792 }
1793 
price()1794 unsigned        ST_ItemIterator::price()
1795 {
1796     return m_price;
1797 }
1798 
operator ++()1799 const ST_ItemIterator& ST_ItemIterator::operator ++ ()
1800 {
1801     m_item = nullptr;
1802     m_shop = nullptr;
1803 
1804     const LevelStashes &ls = m_stash_level_it->second;
1805 
1806     if (m_stash_it == ls.m_stashes.end())
1807     {
1808         if (m_shop_it == ls.m_shops.end())
1809         {
1810             ++m_stash_level_it;
1811             if (m_stash_level_it == StashTrack.levels.end())
1812                 return *this;
1813 
1814             new_level();
1815             return *this;
1816         }
1817         m_shop = &(*m_shop_it);
1818 
1819         if (m_shop_item_it != m_shop->shop.stock.end())
1820         {
1821             const item_def &item = *m_shop_item_it++;
1822             m_item  = &item;
1823             ASSERT(m_item->defined());
1824             m_price = item_price(item, m_shop->shop);
1825             return *this;
1826         }
1827 
1828         ++m_shop_it;
1829         if (m_shop_it != ls.m_shops.end())
1830             m_shop_item_it = m_shop_it->shop.stock.begin();
1831 
1832         ++(*this);
1833     }
1834     else
1835     {
1836         if (m_stash_item_it != m_stash_it->second.items.end())
1837         {
1838             m_item = &(*m_stash_item_it++);
1839             ASSERT(m_item->defined());
1840             return *this;
1841         }
1842 
1843         ++m_stash_it;
1844         if (m_stash_it == ls.m_stashes.end())
1845         {
1846             ++(*this);
1847             return *this;
1848         }
1849 
1850         m_stash_item_it = m_stash_it->second.items.begin();
1851         ++(*this);
1852     }
1853 
1854     return *this;
1855 }
1856 
new_level()1857 void ST_ItemIterator::new_level()
1858 {
1859     m_item  = nullptr;
1860     m_shop  = nullptr;
1861     m_price = 0;
1862 
1863     if (m_stash_level_it == StashTrack.levels.end())
1864         return;
1865 
1866     const LevelStashes &ls = m_stash_level_it->second;
1867 
1868     m_place = ls.m_place;
1869 
1870     m_stash_it = ls.m_stashes.begin();
1871     if (m_stash_it != ls.m_stashes.end())
1872     {
1873         m_stash_item_it = m_stash_it->second.items.begin();
1874         if (m_stash_item_it != m_stash_it->second.items.end())
1875         {
1876             m_item = &(*m_stash_item_it++);
1877             ASSERT(m_item->defined());
1878         }
1879     }
1880 
1881     m_shop_it = ls.m_shops.begin();
1882     if (m_shop_it != ls.m_shops.end())
1883     {
1884         const ShopInfo &si = *m_shop_it;
1885 
1886         m_shop_item_it = si.shop.stock.begin();
1887 
1888         if (m_item == nullptr && m_shop_item_it != si.shop.stock.end())
1889         {
1890             const item_def &item = *m_shop_item_it++;
1891             m_item  = &item;
1892             ASSERT(m_item->defined());
1893             m_price = item_price(item, si.shop);
1894             m_shop  = &si;
1895         }
1896     }
1897 }
1898 
operator ++(int)1899 ST_ItemIterator ST_ItemIterator::operator ++ (int)
1900 {
1901     const ST_ItemIterator copy = *this;
1902     ++(*this);
1903     return copy;
1904 }
1905