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