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 ¤t = 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 = <pat;
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