1 /**
2  * @file
3  * @brief Records location of stairs etc
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "dgn-overview.h"
9 
10 #include <algorithm>
11 #include <cstdio>
12 #include <cstdlib>
13 #include <cstring>
14 
15 #include "branch.h"
16 #include "command.h"
17 #include "describe.h"
18 #include "env.h"
19 #include "feature.h"
20 #include "files.h"
21 #include "libutil.h"
22 #include "macro.h"
23 #include "menu.h"
24 #include "message.h"
25 #include "prompt.h"
26 #include "religion.h"
27 #include "scroller.h"
28 #include "stairs.h"
29 #include "store.h" //for level_id()
30 #include "stringutil.h"
31 #include "tag-version.h"
32 #include "terrain.h"
33 #include "travel.h"
34 #include "unicode.h"
35 
36 enum annotation_menu_commands
37 {
38     // Annotate one level up
39     ID_UP       = -99,
40 
41     // Annotate the dungeon floor the player character is currently on
42     ID_HERE     = -100,
43 
44     // Annotate one level down
45     ID_DOWN     = -101,
46 
47     // Cancel the whole thing
48     ID_CANCEL   = -102,
49 };
50 
51 typedef map<branch_type, set<level_id> > stair_map_type;
52 typedef map<level_pos, shop_type> shop_map_type;
53 typedef map<level_pos, god_type> altar_map_type;
54 typedef map<level_pos, branch_type> portal_map_type;
55 typedef map<level_pos, string> portal_note_map_type;
56 typedef map<level_id, string> annotation_map_type;
57 typedef pair<string, level_id> monster_annotation;
58 
59 stair_map_type stair_level;
60 shop_map_type shops_present;
61 altar_map_type altars_present;
62 portal_map_type portals_present;
63 portal_note_map_type portal_notes;
64 annotation_map_type level_annotations;
65 annotation_map_type level_exclusions;
66 annotation_map_type level_uniques;
67 // FIXME: this should really be a multiset, in case you get multiple
68 // ghosts with the same name, combo, and XL on the same level.
69 set<monster_annotation> auto_unique_annotations;
70 
71 static void _seen_altar(god_type god, const coord_def& pos);
72 static void _seen_staircase(const coord_def& pos);
73 static void _seen_shop(const coord_def& pos);
74 static void _seen_portal(dungeon_feature_type feat, const coord_def& pos);
75 static void _process_command(const char keypress);
76 
77 static string _get_branches(bool display);
78 static string _get_altars(bool display);
79 static string _get_shops(bool display);
80 static string _get_portals();
81 static string _get_notes(bool display);
82 static string _print_altars_for_gods(const vector<god_type>& gods,
83                                      bool print_unseen, bool display);
84 static const string _get_coloured_level_annotation(level_id li);
85 
overview_clear()86 void overview_clear()
87 {
88     stair_level.clear();
89     shops_present.clear();
90     altars_present.clear();
91     portals_present.clear();
92     portal_notes.clear();
93     level_annotations.clear();
94     level_exclusions.clear();
95     level_uniques.clear();
96     auto_unique_annotations.clear();
97 }
98 
seen_notable_thing(dungeon_feature_type which_thing,const coord_def & pos)99 void seen_notable_thing(dungeon_feature_type which_thing, const coord_def& pos)
100 {
101     // Don't record in temporary terrain
102     if (!player_in_connected_branch())
103         return;
104 
105     const god_type god = feat_altar_god(which_thing);
106     if (god != GOD_NO_GOD)
107         _seen_altar(god, pos);
108     else if (feat_is_branch_entrance(which_thing))
109         _seen_staircase(pos);
110     else if (which_thing == DNGN_ENTER_SHOP)
111         _seen_shop(pos);
112     else if (feat_is_gate(which_thing)) // overinclusive
113         _seen_portal(which_thing, pos);
114 }
115 
move_notable_thing(const coord_def & orig,const coord_def & dest)116 bool move_notable_thing(const coord_def& orig, const coord_def& dest)
117 {
118     ASSERT_IN_BOUNDS(orig);
119     ASSERT_IN_BOUNDS(dest);
120     ASSERT(orig != dest);
121     ASSERT(!is_notable_terrain(env.grid(dest)));
122 
123     if (!is_notable_terrain(env.grid(orig)))
124         return false;
125 
126     level_pos pos1(level_id::current(), orig);
127     level_pos pos2(level_id::current(), dest);
128 
129     if (shops_present.count(pos1))
130         shops_present[pos2]         = shops_present[pos1];
131     if (altars_present.count(pos1))
132         altars_present[pos2]        = altars_present[pos1];
133     if (portals_present.count(pos1))
134         portals_present[pos2]       = portals_present[pos1];
135     if (portal_notes.count(pos1))
136         portal_notes[pos2]          = portal_notes[pos1];
137 
138     unnotice_feature(pos1);
139 
140     return true;
141 }
142 
coloured_branch(branch_type br)143 static string coloured_branch(branch_type br)
144 {
145     if (br < 0 || br >= NUM_BRANCHES)
146         return "<lightred>Buggy buglands</lightred>";
147 
148     return make_stringf("<yellow>%s</yellow>", branches[br].shortname);
149 }
150 
shoptype_to_string(shop_type s)151 static string shoptype_to_string(shop_type s)
152 {
153     switch (s)
154     {
155     case SHOP_WEAPON:          return "<w>(</w>";
156     case SHOP_WEAPON_ANTIQUE:  return "<yellow>(</yellow>";
157     case SHOP_ARMOUR:          return "<w>[</w>";
158     case SHOP_ARMOUR_ANTIQUE:  return "<yellow>[</yellow>";
159     case SHOP_GENERAL:         return "<w>*</w>";
160     case SHOP_GENERAL_ANTIQUE: return "<yellow>*</yellow>";
161     case SHOP_JEWELLERY:       return "<w>=</w>";
162     case SHOP_BOOK:            return "<w>:</w>";
163     case SHOP_DISTILLERY:      return "<w>!</w>";
164     case SHOP_SCROLL:          return "<w>?</w>";
165     default:                   return "<w>x</w>";
166     }
167 }
168 
overview_knows_portal(branch_type portal)169 bool overview_knows_portal(branch_type portal)
170 {
171     for (const auto &entry : portals_present)
172         if (entry.second == portal)
173             return true;
174 
175     return false;
176 }
177 
178 // Ever used only for Pan, Abyss and Hell.
overview_knows_num_portals(dungeon_feature_type portal)179 int overview_knows_num_portals(dungeon_feature_type portal)
180 {
181     int num = 0;
182     for (const auto &entry : portals_present)
183     {
184         if (branches[entry.second].entry_stairs == portal)
185             num++;
186     }
187 
188     return num;
189 }
190 
_portals_description_string()191 static string _portals_description_string()
192 {
193     string disp;
194     level_id    last_id;
195     for (branch_iterator it; it; ++it)
196     {
197         last_id.depth = 10000;
198         for (const auto &entry : portals_present)
199         {
200             // one line per region should be enough, they're all of the form
201             // Branch:XX.
202             if (entry.second == it->id)
203             {
204                 if (last_id.depth == 10000)
205                     disp += coloured_branch(entry.second)+ ":";
206 
207                 if (entry.first.id == last_id)
208                     disp += '*';
209                 else
210                 {
211                     disp += ' ';
212                     disp += entry.first.id.describe(false, true);
213                 }
214                 last_id = entry.first.id;
215 
216                 // Portals notes (Trove price).
217                 const string note = portal_notes[entry.first];
218                 if (!note.empty())
219                     disp += " (" + note + ")";
220             }
221         }
222         if (last_id.depth != 10000)
223             disp += "\n";
224     }
225     return disp;
226 }
227 
228 // display: format for in-game display; !display: format for dump
overview_description_string(bool display)229 string overview_description_string(bool display)
230 {
231     string disp;
232 
233     disp += "                    <white>Dungeon Overview and Level Annotations</white>\n" ;
234     disp += _get_branches(display);
235     disp += _get_altars(display);
236     disp += _get_shops(display);
237     disp += _get_portals();
238     disp += _get_notes(display);
239 
240     return disp.substr(0, disp.find_last_not_of('\n')+1);
241 }
242 
243 // iterate through every dungeon branch, listing the ones which have been found
_get_seen_branches(bool display)244 static string _get_seen_branches(bool display)
245 {
246     // Each branch entry takes up 26 spaces + 38 for tags.
247     const int width = 64;
248 
249     int num_printed_branches = 0;
250     char buffer[100];
251     string disp;
252 
253     disp += "\n<green>Branches:</green>";
254     if (display)
255     {
256         disp += " (press <white>G</white> to reach them and "
257                 "<white>?/B</white> for more information)";
258     }
259     disp += "\n";
260 
261     for (branch_iterator it; it; ++it)
262     {
263         const branch_type branch = it->id;
264 
265         if (branch == BRANCH_ZIGGURAT)
266             continue;
267 
268         if (branch == root_branch
269             || stair_level.count(branch))
270         {
271             // having an entry for branch that is an empty set means a branch
272             // that no longer has any stairs.
273             level_id lid(branch, 0);
274             lid = find_deepest_explored(lid);
275 
276             string entry_desc;
277             for (auto lvl : stair_level[branch])
278                 entry_desc += " " + lvl.describe(false, true);
279 
280             // "D" is a little too short here.
281             const char *brname = (branch == BRANCH_DUNGEON
282                                   ? it->shortname
283                                   : it->abbrevname);
284 
285             if (entry_desc.size() == 0 && branch != BRANCH_DUNGEON
286                 && you.where_are_you != branch)
287             {
288                 // previously visited portal branches
289                 snprintf(buffer, sizeof buffer,
290                     "<yellow>%7s</yellow> <darkgrey>(visited)</darkgrey>",
291                     brname);
292             }
293             else
294             {
295                 snprintf(buffer, sizeof buffer,
296                     "<yellow>%*s</yellow> <darkgrey>(%d/%d)</darkgrey>%s",
297                     branch == root_branch ? -7 : 7,
298                     brname, lid.depth, brdepth[branch], entry_desc.c_str());
299             }
300 
301             disp += buffer;
302             num_printed_branches++;
303 
304             disp += (num_printed_branches % 3) == 0
305                     ? "\n"
306                     : string(max<int>(width - strlen(buffer), 0), ' ');
307         }
308     }
309 
310     if (num_printed_branches % 3 != 0)
311         disp += "\n";
312     return disp;
313 }
314 
_get_unseen_branches()315 static string _get_unseen_branches()
316 {
317     int num_printed_branches = 0;
318     char buffer[100];
319     string disp;
320 
321     for (branch_iterator it; it; ++it)
322     {
323         if (it->id < BRANCH_FIRST_NON_DUNGEON)
324             continue;
325 
326         const branch_type branch = it->id;
327         if (!connected_branch_can_exist(branch))
328             continue;
329 
330         if (branch == BRANCH_VESTIBULE || !is_connected_branch(branch))
331             continue;
332 
333         if (branch_is_unfinished(branch))
334             continue;
335 
336         if (!stair_level.count(branch))
337         {
338             const branch_type parent = it->parent_branch;
339             // Root branches.
340             if (parent == NUM_BRANCHES)
341                 continue;
342             level_id lid(parent, 0);
343             lid = find_deepest_explored(lid);
344             if (lid.depth >= it->mindepth)
345             {
346                 if (it->mindepth != it->maxdepth)
347                 {
348                     snprintf(buffer, sizeof buffer,
349                         "<darkgrey>%6s: %s:%d-%d</darkgrey>",
350                             it->abbrevname,
351                             branches[parent].abbrevname,
352                             it->mindepth,
353                             it->maxdepth);
354                 }
355                 else
356                 {
357                     snprintf(buffer, sizeof buffer,
358                         "<darkgrey>%6s: %s:%d</darkgrey>",
359                             it->abbrevname,
360                             branches[parent].abbrevname,
361                             it->mindepth);
362                 }
363 
364                 disp += buffer;
365                 num_printed_branches++;
366 
367                 disp += (num_printed_branches % 4) == 0
368                         ? "\n"
369                         // Each branch entry takes up 20 spaces
370                         : string(20 + 21 - strlen(buffer), ' ');
371             }
372         }
373     }
374 
375     if (num_printed_branches % 4 != 0)
376         disp += "\n";
377     return disp;
378 }
379 
_get_branches(bool display)380 static string _get_branches(bool display)
381 {
382     return _get_seen_branches(display) + _get_unseen_branches();
383 }
384 
385 // iterate through every god and display their altar's discovery state by colour
_get_altars(bool display)386 static string _get_altars(bool display)
387 {
388     // Just wastes space for demigods.
389     if (you.has_mutation(MUT_FORLORN))
390         return "";
391 
392     string disp;
393 
394     disp += "\n<green>Altars:</green>";
395     if (display)
396     {
397         disp += " (press <white>_</white> to reach them and "
398                 "<white>?/G</white> for information about gods)";
399     }
400     disp += "\n";
401     disp += _print_altars_for_gods(temple_god_list(), true, display);
402     disp += _print_altars_for_gods(nontemple_god_list(), false, display);
403 
404     return disp;
405 }
406 
407 // Loops through gods, printing their altar status by colour.
_print_altars_for_gods(const vector<god_type> & gods,bool print_unseen,bool display)408 static string _print_altars_for_gods(const vector<god_type>& gods,
409                                      bool print_unseen, bool display)
410 {
411     string disp;
412     char buffer[100];
413     int num_printed = 0;
414     char const *colour;
415     const int columns = 4;
416 
417     for (const god_type god : gods)
418     {
419         // for each god, look through the notable altars list for a match
420         bool has_altar_been_seen = false;
421         for (const auto &entry : altars_present)
422         {
423             if (entry.second == god)
424             {
425                 has_altar_been_seen = true;
426                 break;
427             }
428         }
429 
430         // If dumping, only laundry list the seen gods
431         if (!display)
432         {
433             if (has_altar_been_seen)
434                 disp += uppercase_first(god_name(god, false)) + "\n";
435             continue;
436         }
437 
438         colour = "darkgrey";
439         if (has_altar_been_seen)
440             colour = "white";
441         // Good gods don't inflict penance unless they hate your god.
442         if (player_under_penance(god)
443             && (xp_penance(god) || active_penance(god)))
444         {
445             colour = (you.penance[god] > 10) ? "red" : "lightred";
446         }
447         // Indicate good gods that you've abandoned, though.
448         else if (player_under_penance(god))
449             colour = "magenta";
450         else if (you_worship(god))
451             colour = "yellow";
452         else if (god_likes_your_god(god) && has_altar_been_seen)
453             colour = "brown";
454 
455         if (!print_unseen && !strcmp(colour, "darkgrey"))
456             continue;
457 
458         if (is_unavailable_god(god))
459             colour = "darkgrey";
460 
461         string disp_name = uppercase_first(god_name(god, false));
462         if (god == GOD_GOZAG && !you_worship(GOD_GOZAG))
463             disp_name += make_stringf(" ($%d)", gozag_service_fee());
464 
465         snprintf(buffer, sizeof buffer, "<%s>%s</%s>",
466                  colour, disp_name.c_str(), colour);
467         disp += buffer;
468         num_printed++;
469 
470         if (num_printed % columns == 0)
471             disp += "\n";
472         else
473             // manually aligning the god columns: ten spaces between columns
474             switch (num_printed % columns)
475             {
476             case 1: disp += string(19 - strwidth(disp_name), ' ');
477                     break;
478             case 2: disp += string(23 - strwidth(disp_name), ' ');
479                     break;
480             case 3: disp += string(20 - strwidth(disp_name), ' ');
481                     break;
482             }
483     }
484 
485     if (num_printed > 0 && num_printed % columns != 0)
486         disp += "\n";
487     return disp;
488 }
489 
490 // iterate through all discovered shops, printing what level they are on.
_get_shops(bool display)491 static string _get_shops(bool display)
492 {
493     string disp;
494     level_id last_id;
495 
496     if (!shops_present.empty())
497     {
498         disp +="\n<green>Shops:</green>";
499         if (display)
500             disp += " (press <white>$</white> to reach them - yellow denotes antique shop)";
501         disp += "\n";
502     }
503     last_id.depth = 10000;
504 
505     // There are at most 5 shops per level, plus up to 8 chars for the
506     // level name, plus 4 for the spacing (3 as padding + 1 separating
507     // items from level). That makes a total of 17 characters per shop:
508     //       1...5....0....5..
509     // "D:8 *   Vaults:2 **([+   D:24 +";
510     const int maxcolumn = 79 - 17;
511     int column_count = 0;
512 
513     for (const auto &entry : shops_present)
514     {
515         if (entry.first.id != last_id)
516         {
517             const bool existing = you.level_visited(entry.first.id);
518             if (column_count > maxcolumn)
519             {
520                 disp += "\n";
521                 column_count = 0;
522             }
523             else if (column_count != 0)
524             {
525                 disp += "   ";
526                 column_count += 3;
527             }
528             disp += existing ? "<lightgrey>" : "<darkgrey>";
529 
530             const string loc = entry.first.id.describe(false, true);
531             disp += loc;
532             column_count += strwidth(loc);
533 
534             disp += " ";
535             disp += existing ? "</lightgrey>" : "</darkgrey>";
536             column_count += 1;
537 
538             last_id = entry.first.id;
539         }
540         disp += shoptype_to_string(entry.second);
541         ++column_count;
542     }
543 
544     if (!shops_present.empty())
545         disp += "\n";
546 
547     return disp;
548 }
549 
550 // Loop through found portals and display them
_get_portals()551 static string _get_portals()
552 {
553     string disp;
554 
555     if (!portals_present.empty())
556         disp += "\n<green>Portals:</green>\n";
557     disp += _portals_description_string();
558 
559     return disp;
560 }
561 
562 // Loop through each branch, printing stored notes.
_get_notes(bool display)563 static string _get_notes(bool display)
564 {
565     string disp;
566 
567     for (branch_iterator it; it; ++it)
568         for (int d = 1; d <= brdepth[it->id]; ++d)
569         {
570             level_id i(it->id, d);
571             if (!get_level_annotation(i).empty())
572                 disp += _get_coloured_level_annotation(i) + "\n";
573         }
574 
575     if (disp.empty())
576         return disp;
577 
578     if (display)
579         return "\n<green>Annotations:</green> (press <white>!</white> to add a new annotation)\n" + disp;
580     return "\n<green>Annotations:</green>\n" + disp;
581 }
582 
583 template <typename Z, typename Key>
_find_erase(Z & map,const Key & k)584 static inline bool _find_erase(Z &map, const Key &k)
585 {
586     if (map.count(k))
587     {
588         map.erase(k);
589         return true;
590     }
591     return false;
592 }
593 
_unnotice_portal(const level_pos & pos)594 static bool _unnotice_portal(const level_pos &pos)
595 {
596     (void) _find_erase(portal_notes, pos);
597     return _find_erase(portals_present, pos);
598 }
599 
_unnotice_altar(const level_pos & pos)600 static bool _unnotice_altar(const level_pos &pos)
601 {
602     return _find_erase(altars_present, pos);
603 }
604 
_unnotice_shop(const level_pos & pos)605 static bool _unnotice_shop(const level_pos &pos)
606 {
607     return _find_erase(shops_present, pos);
608 }
609 
_unnotice_stair(const level_pos & pos)610 static bool _unnotice_stair(const level_pos &pos)
611 {
612     const dungeon_feature_type feat = env.grid(pos.pos);
613     if (!feat_is_branch_entrance(feat))
614         return false;
615 
616     for (branch_iterator it; it; ++it)
617         if (it->entry_stairs == feat)
618         {
619             const branch_type br = it->id;
620             if (stair_level.count(br))
621             {
622                 stair_level[br].erase(level_id::current());
623                 if (stair_level[br].empty())
624                     stair_level.erase(br);
625                 return true;
626             }
627         }
628 
629     return false;
630 }
631 
unnotice_feature(const level_pos & pos)632 bool unnotice_feature(const level_pos &pos)
633 {
634     StashTrack.remove_shop(pos);
635     shopping_list.forget_pos(pos);
636     return _unnotice_portal(pos)
637         || _unnotice_altar(pos)
638         || _unnotice_shop(pos)
639         || _unnotice_stair(pos);
640 }
641 
642 class dgn_overview : public formatted_scroller
643 {
644 public:
dgn_overview(const string & text="")645     dgn_overview(const string& text = "") : formatted_scroller(FS_PREWRAPPED_TEXT, text) {};
646 
647 private:
process_key(int ch)648     bool process_key(int ch) override
649     {
650         // We handle these after exiting dungeon overview window
651         // to prevent menus from stacking on top of each other.
652         if (ch == 'G' || ch == '_' || ch == '$' || ch =='!')
653             return false;
654         else
655             return formatted_scroller::process_key(ch);
656     }
657 };
658 
display_overview()659 void display_overview()
660 {
661     string disp = overview_description_string(true);
662     linebreak_string(disp, 80);
663     dgn_overview overview(disp);
664     _process_command(overview.show());
665 }
666 
_process_command(const char keypress)667 static void _process_command(const char keypress)
668 {
669     switch (keypress)
670     {
671         case 'G':
672             do_interlevel_travel();
673             return;
674         case '_':
675             if (!altars_present.empty())
676             {
677                 macro_sendkeys_end_add_expanded('_');
678                 do_interlevel_travel();
679             }
680             else
681                 mpr("Sorry, you haven't seen any altar yet.");
682             return;
683         case '$':
684             if (!shops_present.empty())
685                 StashTrack.search_stashes("shop");
686             else
687                 mpr("Sorry, you haven't seen any shop yet.");
688             return;
689         case '!':
690             do_annotate();
691             return;
692         default:
693             return;
694     }
695 }
696 
_seen_staircase(const coord_def & pos)697 static void _seen_staircase(const coord_def& pos)
698 {
699     // Only handles stairs, not gates or arches
700     // Don't worry about:
701     //   - stairs returning to dungeon - predictable
702     //   - entrances to the hells - always in vestibule
703 
704     // If the branch has already been entered, then the new entry is obviously
705     // a mimic, don't add it.
706     const branch_type branch = get_branch_at(pos);
707     if (!branch_entered(branch))
708         stair_level[branch].insert(level_id::current());
709 }
710 
711 // If player has seen an altar; record it.
_seen_altar(god_type god,const coord_def & pos)712 static void _seen_altar(god_type god, const coord_def& pos)
713 {
714     // Can't record in Abyss or Pan.
715     if (!player_in_connected_branch())
716         return;
717 
718     level_pos where(level_id::current(), pos);
719     altars_present[where] = god;
720 }
721 
_seen_shop(const coord_def & pos)722 static void _seen_shop(const coord_def& pos)
723 {
724     shops_present[level_pos(level_id::current(), pos)] = shop_at(pos)->type;
725 }
726 
_seen_portal(dungeon_feature_type which_thing,const coord_def & pos)727 static void _seen_portal(dungeon_feature_type which_thing, const coord_def& pos)
728 {
729     if (feat_is_portal_entrance(which_thing)
730         || which_thing == DNGN_ENTER_ABYSS
731         || which_thing == DNGN_ENTER_PANDEMONIUM
732         || which_thing == DNGN_ENTER_HELL && !player_in_hell())
733     {
734         level_pos where(level_id::current(), pos);
735         portals_present[where] = stair_destination(pos).branch;
736         portal_notes[where] =
737             env.markers.property_at(pos, MAT_ANY, "overview_note");
738     }
739 }
740 
741 #define SEEN_RUNED_DOOR_KEY "num_runed_doors"
742 #define SEEN_TRANSPORTER_KEY "num_transporters"
743 
744 // Get the env prop key we use to track discoveries of this feature.
_get_tracked_feature_key(dungeon_feature_type feat)745 static const char *_get_tracked_feature_key(dungeon_feature_type feat)
746 {
747     switch (feat)
748     {
749         case DNGN_RUNED_DOOR:
750         case DNGN_RUNED_CLEAR_DOOR:
751             return SEEN_RUNED_DOOR_KEY;
752             break;
753         case DNGN_TRANSPORTER:
754             return SEEN_TRANSPORTER_KEY;
755             break;
756         default:
757             die("Unknown tracked feature: %s", get_feature_def(feat).name);
758             return nullptr;
759     }
760 }
761 
762 /**
763  * Update the level annotations for a feature we track in travel cache.
764  *
765  * @param feat       The type of feature we're updating.
766  * @param old_num    The previous count of the feature, so we can determine
767  *                   whether we need to remove the annotation entirely.
768  **/
_update_tracked_feature_annot(dungeon_feature_type feat,int old_num)769 static void _update_tracked_feature_annot(dungeon_feature_type feat,
770                                           int old_num)
771 {
772     const level_id li = level_id::current();
773     const char *feat_key = _get_tracked_feature_key(feat);
774     const int new_num = env.properties[feat_key];
775     const char *feat_desc = get_feature_def(feat).name;
776     const string new_string = make_stringf("%d %s%s", new_num, feat_desc,
777                                            new_num == 1 ? "" : "s");
778     const string old_string = make_stringf("%d %s%s", old_num, feat_desc,
779                                            old_num == 1 ? "" : "s");
780 
781     //TODO: regexes
782     if (old_num > 0 && new_num > 0)
783     {
784        level_annotations[li] = replace_all(level_annotations[li],
785                                            old_string, new_string);
786     }
787     else if (old_num == 0)
788     {
789         if (!level_annotations[li].empty())
790             level_annotations[li] += ", ";
791         level_annotations[li] += new_string;
792     }
793     else if (new_num == 0)
794     {
795         level_annotations[li] = replace_all(level_annotations[li],
796                                             old_string + ", ", "");
797         level_annotations[li] = replace_all(level_annotations[li],
798                                             old_string, "");
799         trim_string(level_annotations[li]);
800         strip_suffix(level_annotations[li], ",");
801     }
802 }
803 
804 /**
805  * Update the count for a newly discovered feature whose discovery/exploration
806  * we track for travel cache purposes.
807  *
808  * @param feat    The type of feature we've just seen.
809  **/
seen_tracked_feature(dungeon_feature_type feat)810 void seen_tracked_feature(dungeon_feature_type feat)
811 {
812     const char *feat_key = _get_tracked_feature_key(feat);
813 
814     if (!env.properties.exists(feat_key))
815         env.properties[feat_key] = 0;
816 
817     _update_tracked_feature_annot(feat, env.properties[feat_key].get_int()++);
818 }
819 
820 /**
821  * Update the count for an explored feature whose discovery/exploration we track
822  * for travel cache purposes.
823  *
824  * @param feat    The type of feature we've just explored.
825  **/
explored_tracked_feature(dungeon_feature_type feat)826 void explored_tracked_feature(dungeon_feature_type feat)
827 {
828     const char *feat_key = _get_tracked_feature_key(feat);
829 #if TAG_MAJOR_VERSION > 34
830     ASSERT(env.properties.exists(feat_key));
831 #endif
832 
833     // Opening a runed door we haven't seen (because of door_vault, probably).
834     if (env.properties[feat_key].get_int() == 0)
835         return;
836 
837     _update_tracked_feature_annot(feat, env.properties[feat_key].get_int()--);
838 }
839 
enter_branch(branch_type branch,level_id from)840 void enter_branch(branch_type branch, level_id from)
841 {
842     // this will ensure that branch is in stair_level either way
843     // TODO: track stair levels for portal branches somehow?
844     if (stair_level[branch].size() > 1)
845     {
846         stair_level[branch].clear();
847         stair_level[branch].insert(from);
848     }
849 }
850 
851 // Add an annotation on a level if we corrupt with Lugonu's ability
mark_corrupted_level(level_id li)852 void mark_corrupted_level(level_id li)
853 {
854     if (!level_annotations[li].empty())
855         level_annotations[li] += ", ";
856     level_annotations[li] += "corrupted";
857 }
858 
859 ////////////////////////////////////////////////////////////////////////
860 
_update_unique_annotation(level_id level)861 static void _update_unique_annotation(level_id level)
862 {
863     string note = "";
864     string sep = ", ";
865 
866     for (const auto &annot : auto_unique_annotations)
867         if (annot.first.find(',') != string::npos)
868             sep = "; ";
869 
870     for (const auto &annot : auto_unique_annotations)
871     {
872         if (annot.second == level)
873         {
874             if (note.length() > 0)
875                 note += sep;
876             note += annot.first;
877         }
878     }
879     if (note.empty())
880         level_uniques.erase(level);
881     else
882         level_uniques[level] = note;
883 }
884 
unique_name(monster * mons)885 static string unique_name(monster* mons)
886 {
887     string name = mons->name(DESC_PLAIN, true);
888     if (mons->type == MONS_PLAYER_GHOST)
889         name += ", " + short_ghost_description(mons, true);
890     else
891     {
892         if (strstr(name.c_str(), "Royal Jelly"))
893             name = "Royal Jelly";
894         if (strstr(name.c_str(), "Lernaean hydra"))
895             name = "Lernaean hydra";
896         if (strstr(name.c_str(), "Serpent of Hell"))
897             name = "Serpent of Hell";
898         if (strstr(name.c_str(), "Blork"))
899             name = "Blork the orc";
900     }
901     return name;
902 }
903 
set_unique_annotation(monster * mons,const level_id level)904 void set_unique_annotation(monster* mons, const level_id level)
905 {
906     if (!mons_is_or_was_unique(*mons)
907         && mons->type != MONS_PLAYER_GHOST
908         || testbits(mons->flags, MF_SPECTRALISED)
909         || mons->is_illusion()
910         || mons->props.exists("no_annotate")
911             && mons->props["no_annotate"].get_bool())
912 
913     {
914         return;
915     }
916 
917     remove_unique_annotation(mons);
918     auto_unique_annotations.insert(make_pair(unique_name(mons), level));
919     _update_unique_annotation(level);
920 }
921 
remove_unique_annotation(monster * mons)922 void remove_unique_annotation(monster* mons)
923 {
924     if (mons->is_illusion()) // Fake monsters don't clear real annotations
925         return;
926     set<level_id> affected_levels;
927     string name = unique_name(mons);
928     for (auto i = auto_unique_annotations.begin();
929          i != auto_unique_annotations.end();)
930     {
931         // Only remove player ghosts from the current level or that you can see
932         // (e.g. following you on stairs): there may be a different ghost with
933         // the same unique_name elsewhere.
934         if ((mons->type != MONS_PLAYER_GHOST
935              || i->second == level_id::current()
936              || you.can_see(*mons) && testbits(mons->flags, MF_TAKING_STAIRS))
937             && i->first == name)
938         {
939             affected_levels.insert(i->second);
940             auto_unique_annotations.erase(i++);
941         }
942         else
943             ++i;
944     }
945 
946     for (auto lvl : affected_levels)
947         _update_unique_annotation(lvl);
948 }
949 
set_level_exclusion_annotation(string str,level_id li)950 void set_level_exclusion_annotation(string str, level_id li)
951 {
952     if (str.empty())
953     {
954         clear_level_exclusion_annotation(li);
955         return;
956     }
957 
958     level_exclusions[li] = str;
959 }
960 
clear_level_exclusion_annotation(level_id li)961 void clear_level_exclusion_annotation(level_id li)
962 {
963     level_exclusions.erase(li);
964 }
965 
get_level_annotation(level_id li,bool skip_excl,bool skip_uniq,bool use_colour,int colour)966 string get_level_annotation(level_id li, bool skip_excl, bool skip_uniq,
967                             bool use_colour, int colour)
968 {
969     string note = "";
970 
971     if (string *ann = map_find(level_annotations, li))
972         note += use_colour ? colour_string(*ann, colour) : *ann;
973 
974     if (!skip_excl)
975         if (string *excl = map_find(level_exclusions, li))
976         {
977             if (note.length() > 0)
978                 note += ", ";
979             note += *excl;
980         }
981 
982     if (!skip_uniq)
983         if (string *uniq = map_find(level_uniques, li))
984         {
985             if (note.length() > 0)
986                 note += ", ";
987             note += *uniq;
988         }
989 
990     return note;
991 }
992 
_get_coloured_level_annotation(level_id li)993 static const string _get_coloured_level_annotation(level_id li)
994 {
995     string place = "<yellow>" + li.describe() + "</yellow>";
996     int col = level_annotation_has("!", li) ? LIGHTRED : WHITE;
997     return place + " " + get_level_annotation(li, false, false, true, col);
998 }
999 
level_annotation_has(string find,level_id li)1000 bool level_annotation_has(string find, level_id li)
1001 {
1002     string str = get_level_annotation(li);
1003 
1004     return str.find(find) != string::npos;
1005 }
1006 
clear_level_annotations(level_id li)1007 void clear_level_annotations(level_id li)
1008 {
1009     level_annotations.erase(li);
1010 
1011     // Abyss persists its denizens.
1012     if (li == BRANCH_ABYSS)
1013         return;
1014 
1015     for (auto i = auto_unique_annotations.begin(), next = i;
1016          i != auto_unique_annotations.end(); i = next)
1017     {
1018         next = i;
1019         ++next;
1020         if (i->second == li)
1021             auto_unique_annotations.erase(i);
1022     }
1023     level_uniques.erase(li);
1024 }
1025 
marshallUniqueAnnotations(writer & outf)1026 void marshallUniqueAnnotations(writer& outf)
1027 {
1028     marshallShort(outf, auto_unique_annotations.size());
1029     for (const auto &annot : auto_unique_annotations)
1030     {
1031         marshallString(outf, annot.first);
1032         annot.second.save(outf);
1033     }
1034 }
1035 
unmarshallUniqueAnnotations(reader & inf)1036 void unmarshallUniqueAnnotations(reader& inf)
1037 {
1038     auto_unique_annotations.clear();
1039     int num_notes = unmarshallShort(inf);
1040     for (int i = 0; i < num_notes; ++i)
1041     {
1042         string name = unmarshallString(inf);
1043         level_id level;
1044         level.load(inf);
1045         auto_unique_annotations.insert(make_pair(name, level));
1046     }
1047 }
1048 
1049 /**
1050  * Can the player encounter the given connected branch, given their
1051  * knowledge of which have been seen so far?
1052  * @param br A connected branch.
1053  * @returns True if the branch can exist, false otherwise.
1054 */
connected_branch_can_exist(branch_type br)1055 bool connected_branch_can_exist(branch_type br)
1056 {
1057     if (br == BRANCH_SPIDER && stair_level.count(BRANCH_SNAKE)
1058         || br == BRANCH_SNAKE && stair_level.count(BRANCH_SPIDER)
1059         || br == BRANCH_SWAMP && stair_level.count(BRANCH_SHOALS)
1060         || br == BRANCH_SHOALS && stair_level.count(BRANCH_SWAMP))
1061     {
1062         return false;
1063     }
1064 
1065     return true;
1066 }
1067 
1068 /**
1069  * Make the little overview
1070  * (D) Dungeon        (T) Temple         (L) Lair           etc.
1071  * at most 4 branches on 1 line
1072 */
_show_dungeon_overview(vector<branch_type> brs)1073 static void _show_dungeon_overview(vector<branch_type> brs)
1074 {
1075     clear_messages();
1076     int linec = 0;
1077     string line;
1078     for (branch_type br : brs)
1079     {
1080         if (linec == 4)
1081         {
1082             linec = 0;
1083             mpr(line);
1084             line = "";
1085         }
1086         line += make_stringf("(%c) %-14s ",
1087                              branches[br].travel_shortcut,
1088                              branches[br].shortname);
1089         ++linec;
1090     }
1091     if (!line.empty())
1092         mpr(line);
1093     flush_prev_message();
1094     return;
1095 }
1096 
_prompt_annotate_branch(level_id lid)1097 static int _prompt_annotate_branch(level_id lid)
1098 {
1099     // these 3 lines make a vector containing all shown branches.
1100     vector<branch_type> brs;
1101     for (branch_iterator it; it; ++it)
1102         if (is_known_branch_id(it->id))
1103             brs.push_back(it->id);
1104 
1105     mprf(MSGCH_PROMPT, "Annotate which branch? (. - %s, ? - help, ! - show branch list)",
1106         lid.describe(false, true).c_str());
1107 
1108     while (true)
1109     {
1110         int keyin = get_ch();
1111         switch (keyin)
1112         {
1113         CASE_ESCAPE
1114             return ID_CANCEL;
1115         case '?':
1116             show_annotate_help();
1117             break;
1118         case '!':
1119             _show_dungeon_overview(brs);
1120             break;
1121         case '\n': case '\r': case '.':
1122             return ID_HERE;
1123         case '<':
1124             return ID_UP;
1125         case '>':
1126             return ID_DOWN;
1127         case CONTROL('P'):
1128             {
1129                 const branch_type parent = parent_branch(lid.branch);
1130                 if (parent < NUM_BRANCHES)
1131                     return parent;
1132             }
1133             break;
1134         default:
1135             // Is this a branch hotkey?
1136             for (branch_type br : brs)
1137             {
1138                 if (toupper_safe(keyin) == branches[br].travel_shortcut)
1139                     return br;
1140             }
1141             // Otherwise cancel
1142             return ID_CANCEL;
1143         }
1144     }
1145 }
1146 
do_annotate()1147 void do_annotate()
1148 {
1149     const level_id lid  = level_id::current();
1150     const int branch = _prompt_annotate_branch(lid);
1151 
1152     if (branch < 0)
1153     {
1154         ASSERT(ID_CANCEL <= branch && branch <= ID_UP);
1155         annotation_menu_commands a = static_cast<annotation_menu_commands>(branch);
1156         switch (a)
1157         {
1158         case ID_CANCEL:
1159             canned_msg(MSG_OK);
1160             return;
1161         case ID_HERE:
1162             annotate_level(lid);
1163             return;
1164         case ID_UP:
1165             // level_id() is the error vallue of find_up_level(lid)
1166             if (find_up_level(lid) == level_id())
1167                 mpr("There is no level above you.");
1168             else
1169                 annotate_level(find_up_level(lid));
1170             return;
1171         case ID_DOWN:
1172             if (find_down_level(lid) == lid)
1173                 mpr("There is no level below you in this branch.");
1174             else
1175                 annotate_level(find_down_level(lid));
1176             return;
1177         }
1178     }
1179     else
1180     {
1181     int depth;
1182     const int max_depth = branches[branch].numlevels;
1183     // Handle one-level branches by not prompting.
1184     if (max_depth == 1)
1185         depth = 1;
1186     else
1187     {
1188         clear_messages();
1189         const string prompt = make_stringf ("What level of %s? ",
1190                     branches[branch].longname);
1191         depth = prompt_for_int(prompt.c_str(), true);
1192     }
1193     if (depth > 0 && depth <= max_depth)
1194     {
1195         const branch_type br = branch_type(branch);
1196         annotate_level(level_id(br, depth));
1197     }
1198     else
1199         mpr("That's not a valid depth.");
1200     }
1201 }
1202 
annotate_level(level_id li)1203 void annotate_level(level_id li)
1204 {
1205     const string old = get_level_annotation(li, true, true);
1206     if (!old.empty())
1207     {
1208         mprf(MSGCH_PROMPT, "Current level annotation: <lightgrey>%s</lightgrey>",
1209              old.c_str());
1210     }
1211 
1212     const string prompt = "New annotation for " + li.describe()
1213                           + " (include '!' for warning): ";
1214 
1215     char buf[77];
1216     if (msgwin_get_line_autohist(prompt, buf, sizeof(buf), old))
1217         canned_msg(MSG_OK);
1218     else if (old == buf)
1219         canned_msg(MSG_OK);
1220     else if (*buf)
1221         level_annotations[li] = string(buf);
1222     else
1223     {
1224         mpr("Cleared annotation.");
1225         level_annotations.erase(li);
1226     }
1227 }
1228