1 /**
2 * @file
3 * @brief Dumps character info out to the morgue file.
4 **/
5
6 #include "AppHdr.h"
7
8 #include "chardump.h"
9
10 #include <string>
11 #include <cctype>
12 #include <cinttypes>
13 #include <cstdio>
14 #include <cstdlib>
15 #include <cstring>
16 #include <fcntl.h>
17 #if defined(UNIX) || defined(TARGET_COMPILER_MINGW)
18 #include <unistd.h>
19 #endif
20
21 #include "ability.h"
22 #include "artefact.h"
23 #include "art-enum.h"
24 #include "branch.h"
25 #include "describe.h"
26 #include "dgn-overview.h"
27 #include "dungeon.h"
28 #include "fight.h"
29 #include "files.h"
30 #include "god-prayer.h"
31 #include "hiscores.h"
32 #include "initfile.h"
33 #include "invent.h"
34 #include "item-prop.h"
35 #include "items.h"
36 #include "kills.h"
37 #include "libutil.h"
38 #include "melee-attack.h"
39 #include "message.h"
40 #include "mutation.h"
41 #include "notes.h"
42 #include "output.h"
43 #include "place.h"
44 #include "prompt.h"
45 #include "religion.h"
46 #include "scroller.h"
47 #include "showsymb.h"
48 #include "skills.h"
49 #include "spl-book.h"
50 #include "spl-util.h"
51 #include "state.h"
52 #include "stringutil.h"
53 #include "tag-version.h"
54 #include "transform.h"
55 #include "travel.h"
56 #include "unicode.h"
57 #include "version.h"
58 #include "viewchar.h"
59 #include "view.h"
60 #include "xom.h"
61
62 struct dump_params;
63
64 static void _sdump_header(dump_params &);
65 static void _sdump_stats(dump_params &);
66 static void _sdump_location(dump_params &);
67 static void _sdump_religion(dump_params &);
68 static void _sdump_transform(dump_params &);
69 static void _sdump_visits(dump_params &);
70 static void _sdump_gold(dump_params &);
71 static void _sdump_misc(dump_params &);
72 static void _sdump_turns_by_place(dump_params &);
73 static void _sdump_notes(dump_params &);
74 static void _sdump_screenshots(dump_params &);
75 static void _sdump_inventory(dump_params &);
76 static void _sdump_skills(dump_params &);
77 static void _sdump_spells(dump_params &);
78 static void _sdump_mutations(dump_params &);
79 static void _sdump_messages(dump_params &);
80 static void _sdump_screenshot(dump_params &);
81 static void _sdump_kills_by_place(dump_params &);
82 static void _sdump_kills(dump_params &);
83 static void _sdump_xp_by_level(dump_params &);
84 static void _sdump_newline(dump_params &);
85 static void _sdump_overview(dump_params &);
86 static void _sdump_hiscore(dump_params &);
87 static void _sdump_monster_list(dump_params &);
88 static void _sdump_vault_list(dump_params &);
89 static void _sdump_skill_gains(dump_params &);
90 static void _sdump_action_counts(dump_params &);
91 static void _sdump_separator(dump_params &);
92 static void _sdump_lua(dump_params &);
93 static bool _write_dump(const string &fname, const dump_params &,
94 bool print_dump_path = false);
95
96 struct dump_section_handler
97 {
98 const char *name;
99 void (*handler)(dump_params &);
100 };
101
102 struct dump_params
103 {
104 string text;
105 string section;
106 bool full_id;
107 const scorefile_entry *se;
108
dump_paramsdump_params109 dump_params(const string &sec = "", bool id = false,
110 const scorefile_entry *s = nullptr)
111 : section(sec), full_id(id), se(s)
112 {
113 // Start with enough room for 100 80 character lines.
114 text.reserve(100 * 80);
115 }
116 };
117
118 static dump_section_handler dump_handlers[] =
119 {
120 { "header", _sdump_header },
121 { "stats", _sdump_stats },
122 { "location", _sdump_location },
123 { "religion", _sdump_religion },
124 { "transform", _sdump_transform },
125 { "visits", _sdump_visits },
126 { "gold", _sdump_gold },
127 { "misc", _sdump_misc },
128 { "turns_by_place", _sdump_turns_by_place},
129 { "notes", _sdump_notes },
130 { "screenshots", _sdump_screenshots },
131 { "inventory", _sdump_inventory },
132 { "skills", _sdump_skills },
133 { "spells", _sdump_spells },
134 { "mutations", _sdump_mutations },
135 { "messages", _sdump_messages },
136 { "screenshot", _sdump_screenshot },
137 { "kills_by_place", _sdump_kills_by_place},
138 { "kills", _sdump_kills },
139 { "xp_by_level", _sdump_xp_by_level },
140 { "overview", _sdump_overview },
141 { "hiscore", _sdump_hiscore },
142 { "monlist", _sdump_monster_list },
143 { "vaults", _sdump_vault_list },
144 { "spell_usage", _sdump_action_counts }, // compat
145 { "action_counts", _sdump_action_counts },
146 { "skill_gains", _sdump_skill_gains },
147
148 // Conveniences for the .crawlrc artist.
149 { "", _sdump_newline },
150 { "-", _sdump_separator },
151
152 { nullptr, _sdump_lua }
153 };
154
dump_section(dump_params & par)155 static void dump_section(dump_params &par)
156 {
157 for (int i = 0; ; ++i)
158 {
159 if (!dump_handlers[i].name || par.section == dump_handlers[i].name)
160 {
161 if (dump_handlers[i].handler)
162 (*dump_handlers[i].handler)(par);
163 break;
164 }
165 }
166 }
167
_get_dump(bool full_id=false,const scorefile_entry * se=nullptr)168 static dump_params _get_dump(bool full_id = false,
169 const scorefile_entry *se = nullptr)
170 {
171 dump_params par("", full_id, se);
172
173 for (const string §ion : Options.dump_order)
174 {
175 par.section = section;
176 dump_section(par);
177 }
178
179 // Hopefully we get RVO so we don't have to copy the text.
180 return par;
181 }
182
dump_char(const string & fname,bool quiet,bool full_id,const scorefile_entry * se)183 bool dump_char(const string &fname, bool quiet, bool full_id,
184 const scorefile_entry *se)
185 {
186 return _write_dump(fname, _get_dump(full_id, se), quiet);
187 }
188
seed_description()189 string seed_description()
190 {
191 return make_stringf(
192 "Game seed: %" PRIu64 "%s", crawl_state.seed,
193 crawl_state.type == GAME_TYPE_CUSTOM_SEED
194 ? " (custom seed)"
195 : you.deterministic_levelgen ? "" : " (classic levelgen)");
196 }
197
_sdump_header(dump_params & par)198 static void _sdump_header(dump_params &par)
199 {
200 string type = crawl_state.game_type_name();
201 if (type.empty())
202 type = CRAWL;
203 else
204 type += " DCSS";
205
206 par.text += " " + type + " version " + Version::Long;
207 #ifdef USE_TILE_LOCAL
208 par.text += " (tiles)";
209 #elif defined(USE_TILE_WEB)
210 if (::tiles.is_controlled_from_web())
211 par.text += " (webtiles)";
212 else
213 par.text += " (console)";
214 #else
215 par.text += " (console)";
216 #endif
217 par.text += " character file.\n\n";
218
219 if (you.fully_seeded
220 #ifdef DGAMELAUNCH
221 && (par.se // for online games, show seed for a dead char
222 || you.wizard
223 || crawl_state.type == GAME_TYPE_CUSTOM_SEED)
224 #endif
225 )
226 {
227 par.text += seed_description() + "\n\n";
228 }
229 }
230
_sdump_stats(dump_params & par)231 static void _sdump_stats(dump_params &par)
232 {
233 par.text += dump_overview_screen(par.full_id);
234 par.text += "\n\n";
235 }
236
_sdump_transform(dump_params & par)237 static void _sdump_transform(dump_params &par)
238 {
239 string &text(par.text);
240 if (you.form != transformation::none)
241 text += get_form()->get_description(par.se) + "\n\n";}
242
243 static branch_type single_portals[] =
244 {
245 BRANCH_TROVE,
246 BRANCH_SEWER,
247 BRANCH_OSSUARY,
248 BRANCH_BAILEY,
249 BRANCH_GAUNTLET,
250 BRANCH_ICE_CAVE,
251 BRANCH_VOLCANO,
252 BRANCH_WIZLAB,
253 BRANCH_DESOLATION,
254 #if TAG_MAJOR_VERSION == 34
255 BRANCH_LABYRINTH,
256 #endif
257 };
258
_sdump_visits(dump_params & par)259 static void _sdump_visits(dump_params &par)
260 {
261 string &text(par.text);
262
263 string have = "have ";
264 string seen = "seen";
265 if (par.se) // you died -> past tense
266 {
267 have = "";
268 seen = "saw";
269 }
270
271 const vector<PlaceInfo> branches_visited = you.get_all_place_info(true, true);
272
273 PlaceInfo branches_total;
274 for (const PlaceInfo &branch : branches_visited)
275 branches_total += branch;
276
277 text += make_stringf("You %svisited %d branch",
278 have.c_str(), (int)branches_visited.size());
279 if (branches_visited.size() != 1)
280 text += "es";
281 if (brdepth[root_branch] > 1 || branches_visited.size() != 1)
282 {
283 text += make_stringf(" of the dungeon, and %s %d of its levels.\n",
284 seen.c_str(), branches_total.levels_seen);
285 }
286
287 {
288 const PlaceInfo place_info = you.get_place_info(BRANCH_PANDEMONIUM);
289 if (place_info.num_visits > 0)
290 {
291 text += make_stringf("You %svisited Pandemonium %d time",
292 have.c_str(), place_info.num_visits);
293 if (place_info.num_visits > 1)
294 text += "s";
295 text += make_stringf(", and %s %d of its levels.\n",
296 seen.c_str(), place_info.levels_seen);
297 }
298 }
299
300 {
301 const PlaceInfo place_info = you.get_place_info(BRANCH_ABYSS);
302 if (place_info.num_visits > 0)
303 {
304 text += make_stringf("You %svisited the Abyss %d time",
305 have.c_str(), place_info.num_visits);
306 if (place_info.num_visits > 1)
307 text += "s";
308 text += ".\n";
309 }
310 }
311
312 {
313 const PlaceInfo place_info = you.get_place_info(BRANCH_BAZAAR);
314 if (place_info.num_visits > 0)
315 {
316 text += make_stringf("You %svisited %d bazaar",
317 have.c_str(), place_info.num_visits);
318 if (place_info.num_visits > 1)
319 text += "s";
320 text += ".\n";
321 }
322 }
323
324 {
325 const PlaceInfo place_info = you.get_place_info(BRANCH_ZIGGURAT);
326 if (place_info.num_visits > 0)
327 {
328 int num_zigs = place_info.num_visits;
329 text += make_stringf("You %s%s %d ziggurat",
330 have.c_str(),
331 (num_zigs == you.zigs_completed) ? "completed"
332 : "visited",
333 num_zigs);
334 if (num_zigs > 1)
335 text += "s";
336 if (num_zigs != you.zigs_completed && you.zigs_completed)
337 text += make_stringf(" (completing %d)", you.zigs_completed);
338 text += make_stringf(", and %s %d of %s levels",
339 seen.c_str(), place_info.levels_seen,
340 num_zigs > 1 ? "their" : "its");
341 if (num_zigs != 1 && !you.zigs_completed)
342 text += make_stringf(" (deepest: %d)", you.zig_max);
343 text += ".\n";
344 }
345 }
346
347 vector<string> misc_portals;
348 for (branch_type br : single_portals)
349 {
350 const PlaceInfo place_info = you.get_place_info(br);
351 if (!place_info.num_visits)
352 continue;
353 string name = branches[br].shortname;
354 if (place_info.num_visits > 1)
355 name += make_stringf(" (%d times)", place_info.num_visits);
356 misc_portals.push_back(name);
357 }
358 if (!misc_portals.empty())
359 {
360 text += "You " + have + "also visited: "
361 + comma_separated_line(misc_portals.begin(),
362 misc_portals.end())
363 + ".\n";
364 }
365
366 text += "\n";
367 }
368
_sdump_gold(dump_params & par)369 static void _sdump_gold(dump_params &par)
370 {
371 string &text(par.text);
372
373 int lines = 0;
374
375 const char* have = "have ";
376 if (par.se) // you died -> past tense
377 have = "";
378
379 if (you.attribute[ATTR_GOLD_FOUND] > 0)
380 {
381 lines++;
382 text += make_stringf("You %scollected %d gold pieces.\n", have,
383 you.attribute[ATTR_GOLD_FOUND]);
384 }
385
386 if (you.attribute[ATTR_PURCHASES] > 0)
387 {
388 lines++;
389 text += make_stringf("You %sspent %d gold pieces at shops.\n", have,
390 you.attribute[ATTR_PURCHASES]);
391 }
392
393 if (you.attribute[ATTR_DONATIONS] > 0)
394 {
395 lines++;
396 text += make_stringf("You %sdonated %d gold pieces to Zin.\n", have,
397 you.attribute[ATTR_DONATIONS]);
398 }
399
400 if (you.attribute[ATTR_GOZAG_GOLD_USED] > 0)
401 {
402 lines++;
403 text += make_stringf("You %spaid %d gold pieces to Gozag.\n", have,
404 you.attribute[ATTR_GOZAG_GOLD_USED]);
405 }
406
407 if (you.attribute[ATTR_MISC_SPENDING] > 0)
408 {
409 lines++;
410 text += make_stringf("You %sused %d gold pieces for miscellaneous "
411 "purposes.\n", have,
412 you.attribute[ATTR_MISC_SPENDING]);
413 }
414
415 if (lines > 0)
416 text += "\n";
417 }
418
_sdump_misc(dump_params & par)419 static void _sdump_misc(dump_params &par)
420 {
421 _sdump_location(par);
422 _sdump_religion(par);
423 _sdump_transform(par);
424 _sdump_visits(par);
425 _sdump_gold(par);
426 }
427
428 #define TO_PERCENT(x, y) (100.0f * (static_cast<float>(x)) / (static_cast<float>(y)))
429
_denanify(const string & s)430 static string _denanify(const string &s)
431 {
432 string out = replace_all(s, " nan ", " N/A ");
433 out = replace_all(out, " -nan ", " N/A ");
434 out = replace_all(out, " 1#IND ", " N/A ");
435 out = replace_all(out, " -1#IND ", " N/A ");
436 return out;
437 }
438
_sdump_level_xp_info(LevelXPInfo xp_info,string name="")439 static string _sdump_level_xp_info(LevelXPInfo xp_info, string name = "")
440 {
441 string out;
442
443 if (name.empty())
444 name = xp_info.level.describe();
445
446 float c, f;
447 unsigned int total_xp = xp_info.vault_xp + xp_info.non_vault_xp;
448 unsigned int total_count = xp_info.vault_count + xp_info.non_vault_count;
449
450 c = TO_PERCENT(xp_info.vault_xp, total_xp);
451 f = TO_PERCENT(xp_info.vault_count, total_count);
452
453 out =
454 make_stringf("%11s | %7d | %7d | %5.1f | %7d | %7d | %5.1f\n",
455 name.c_str(), xp_info.non_vault_xp, xp_info.vault_xp,
456 c, xp_info.non_vault_count, xp_info.vault_count, f);
457
458 return _denanify(out);
459 }
460
_sdump_turns_place_info(const PlaceInfo place_info,string name="")461 static string _sdump_turns_place_info(const PlaceInfo place_info, string name = "")
462 {
463 string out;
464
465 if (name.empty())
466 name = place_info.short_name();
467
468 unsigned int non_interlevel =
469 place_info.elapsed_total / 10 - place_info.elapsed_interlevel / 10;
470
471 const float g = static_cast<float>(place_info.elapsed_total / 10)
472 / static_cast<float>(place_info.levels_seen);
473
474 out =
475 make_stringf("%14s | %6d | %6d | %6d | %6d | %6d | %3d | %6.1f |\n",
476 name.c_str(),
477 place_info.elapsed_total / 10,
478 non_interlevel,
479 place_info.elapsed_interlevel / 10,
480 place_info.elapsed_resting / 10,
481 place_info.elapsed_explore / 10,
482 place_info.levels_seen,
483 g);
484
485 return _denanify(out);
486 }
487
_sdump_turns_by_place(dump_params & par)488 static void _sdump_turns_by_place(dump_params &par)
489 {
490 string &text(par.text);
491
492 const vector<PlaceInfo> all_visited = you.get_all_place_info(true);
493
494 text +=
495 "Table legend: (Time is in decaauts)\n"
496 " A = Elapsed time spent in this place.\n"
497 " B = Non-inter-level travel time spent in this place.\n"
498 " C = Inter-level travel time spent in this place.\n"
499 " D = Time resting spent in this place.\n"
500 " E = Time spent auto-exploring this place.\n"
501 " F = Levels seen in this place.\n"
502 " G = Mean time per level.\n";
503
504 text += " ";
505 text += " A B C D E F G\n";
506 text += " ";
507 text += "+--------+--------+--------+--------+--------+-----+--------+\n";
508
509 text += _sdump_turns_place_info(you.global_info, "Total");
510
511 for (const PlaceInfo &pi : all_visited)
512 text += _sdump_turns_place_info(pi);
513
514 text += " ";
515 text += "+--------+--------+--------+--------+--------+-----+--------+\n";
516
517 text += "\n";
518
519 CrawlHashTable &time_tracking = you.props[TIME_PER_LEVEL_KEY].get_table();
520 vector<pair<int, string>> to_sort;
521 for (auto &l : time_tracking)
522 if (l.first != "upgrade" && l.first != "Pan")
523 to_sort.emplace_back(l.second.get_int(), l.first);
524 if (to_sort.size() == 0)
525 return; // turn 0 game
526 sort(to_sort.begin(), to_sort.end());
527 reverse(to_sort.begin(), to_sort.end());
528
529 text += "Top non-repeatable levels by time:\n";
530 for (unsigned int i = 0; i < 15 && i < to_sort.size(); i++)
531 text += make_stringf("%8s: %d daAuts\n", to_sort[i].second.c_str(), to_sort[i].first / 10);
532 if (time_tracking.exists("upgrade"))
533 text += "Note: time per level data comes from an upgraded game and may be incomplete.\n";
534 text += "\n";
535 }
536
_sdump_xp_by_level(dump_params & par)537 static void _sdump_xp_by_level(dump_params &par)
538 {
539 string &text(par.text);
540
541 vector<LevelXPInfo> all_info = you.get_all_xp_info(true);
542
543 text +=
544 "Table legend:\n"
545 " A = Non-vault XP\n"
546 " B = Vault XP\n"
547 " C = Vault XP percentage of total XP\n"
548 " D = Non-vault monster count\n"
549 " E = Vault monster count\n"
550 " F = Vault count percentage of total count\n\n";
551
552 text += " ";
553 text += " A B C D E F \n";
554 text += " ";
555 text += "+---------+---------+-------+---------+---------+-------\n";
556
557 text += _sdump_level_xp_info(you.global_xp_info, "Total");
558
559 text += " ";
560 text += "+---------+---------+-------+---------+---------+-------\n";
561
562 for (const LevelXPInfo &mi : all_info)
563 text += _sdump_level_xp_info(mi);
564
565 text += " ";
566 text += "+---------+---------+-------+---------+---------+-------\n";
567
568 text += "\n";
569 }
570
_sdump_newline(dump_params & par)571 static void _sdump_newline(dump_params &par)
572 {
573 par.text += "\n";
574 }
575
_sdump_separator(dump_params & par)576 static void _sdump_separator(dump_params &par)
577 {
578 par.text += string(79, '-') + "\n";
579 }
580
581 // Assume this is an arbitrary Lua function name, call the function and
582 // dump whatever it returns.
_sdump_lua(dump_params & par)583 static void _sdump_lua(dump_params &par)
584 {
585 string luatext;
586 if (!clua.callfn(par.section.c_str(), ">s", &luatext)
587 && !clua.error.empty())
588 {
589 par.text += "Lua dump error: " + clua.error + "\n";
590 }
591 else
592 par.text += luatext;
593 }
594
chardump_desc(const item_def & item)595 string chardump_desc(const item_def& item)
596 {
597 string desc = get_item_description(item, false, true);
598 string outs;
599
600 outs.reserve(desc.length() + 32);
601
602 const int indent = 3;
603
604 if (desc.empty()) // always at least an empty line
605 return "\n";
606
607 while (!desc.empty())
608 {
609 outs += string(indent, ' ')
610 + wordwrap_line(desc, 79 - indent)
611 + "\n";
612 }
613
614 return outs;
615 }
616
_sdump_messages(dump_params & par)617 static void _sdump_messages(dump_params &par)
618 {
619 // A little message history:
620 if (Options.dump_message_count > 0)
621 {
622 par.text += "Message History\n\n";
623 par.text += get_last_messages(Options.dump_message_count);
624 }
625 }
626
_sdump_screenshot(dump_params & par)627 static void _sdump_screenshot(dump_params &par)
628 {
629 par.text += screenshot();
630 par.text += "\n\n";
631 }
632
_sdump_screenshots(dump_params & par)633 static void _sdump_screenshots(dump_params &par)
634 {
635 string &text(par.text);
636 if (note_list.empty())
637 return;
638
639 text += "Illustrated notes\n\n";
640
641 for (const Note ¬e : note_list)
642 {
643 if (note.hidden() || note.type != NOTE_USER_NOTE || note.screen.length() == 0)
644 continue;
645
646 text += note.screen;
647 text += "\n";
648 text += make_stringf("Turn %d on ", note.turn);
649 text += note.place.describe() + ": ";
650 text += note.name;
651 text += "\n\n";
652 }
653 }
654
_sdump_notes(dump_params & par)655 static void _sdump_notes(dump_params &par)
656 {
657 string &text(par.text);
658 if (note_list.empty())
659 return;
660
661 text += "Notes\n";
662 text += "Turn | Place | Note\n";
663 text += "-------+----------+-------------------------------------------\n";
664 for (const Note ¬e : note_list)
665 {
666 if (note.hidden())
667 continue;
668
669 string prefix = note.describe(true, true, false);
670 string suffix = note.describe(false, false, true);
671 if (suffix.empty())
672 continue;
673 int spaceleft = 80 - prefix.length() - 1; // Use 100 cols
674 if (spaceleft <= 0)
675 return;
676
677 linebreak_string(suffix, spaceleft);
678 vector<string> parts = split_string("\n", suffix);
679 if (parts.empty()) // Disregard pure-whitespace notes.
680 continue;
681
682 text += prefix + parts[0] + "\n";
683 for (unsigned int j = 1; j < parts.size(); ++j)
684 text += string(prefix.length()-2, ' ') + string("| ") + parts[j] + "\n";
685 }
686 text += "\n";
687 }
688
_sdump_location(dump_params & par)689 static void _sdump_location(dump_params &par)
690 {
691 if (you.depth == 0 && player_in_branch(BRANCH_DUNGEON))
692 par.text += "You escaped";
693 else if (par.se)
694 par.text += "You were " + prep_branch_level_name();
695 else
696 par.text += "You are " + prep_branch_level_name();
697
698 par.text += ".";
699 par.text += "\n";
700 }
701
_sdump_religion(dump_params & par)702 static void _sdump_religion(dump_params &par)
703 {
704 string &text(par.text);
705 if (!you_worship(GOD_NO_GOD))
706 {
707 if (par.se)
708 text += "You worshipped ";
709 else
710 text += "You worship ";
711 text += god_name(you.religion);
712 text += ".\n";
713
714 if (!you_worship(GOD_XOM))
715 {
716 if (!player_under_penance())
717 {
718 text += god_prayer_reaction();
719 text += "\n";
720 }
721 else
722 {
723 string verb = par.se ? "was" : "is";
724
725 text += uppercase_first(god_name(you.religion));
726 text += " " + verb + " demanding penance.\n";
727 }
728 }
729 else
730 {
731 if (par.se)
732 text += "You were ";
733 else
734 text += "You are ";
735 text += describe_xom_favour();
736 text += "\n";
737 }
738 }
739 }
740
_dump_item_origin(const item_def & item)741 static bool _dump_item_origin(const item_def &item)
742 {
743 #define fs(x) (flags & (x))
744 const int flags = Options.dump_item_origins;
745 if (flags == IODS_EVERYTHING)
746 return true;
747
748 if (fs(IODS_ARTEFACTS)
749 && is_artefact(item) && item_ident(item, ISFLAG_KNOW_PROPERTIES))
750 {
751 return true;
752 }
753 if (fs(IODS_EGO_ARMOUR) && item.base_type == OBJ_ARMOUR
754 && item_type_known(item))
755 {
756 const int spec_ench = get_armour_ego_type(item);
757 return spec_ench != SPARM_NORMAL;
758 }
759
760 if (fs(IODS_EGO_WEAPON) && item.base_type == OBJ_WEAPONS
761 && item_type_known(item))
762 {
763 return get_weapon_brand(item) != SPWPN_NORMAL;
764 }
765
766 if (fs(IODS_JEWELLERY) && item.base_type == OBJ_JEWELLERY)
767 return true;
768
769 if (fs(IODS_RUNES) && item.base_type == OBJ_RUNES)
770 return true;
771
772 if (fs(IODS_STAVES) && item.base_type == OBJ_STAVES)
773 return true;
774
775 if (fs(IODS_BOOKS) && item.base_type == OBJ_BOOKS)
776 return true;
777
778 const int refpr = Options.dump_item_origin_price;
779 if (refpr == -1)
780 return false;
781 return (int)item_value(item, false) >= refpr;
782 #undef fs
783 }
784
_sdump_inventory(dump_params & par)785 static void _sdump_inventory(dump_params &par)
786 {
787 int i;
788
789 string &text(par.text);
790
791 int inv_class2[NUM_OBJECT_CLASSES] = { 0, };
792 int inv_count = 0;
793
794 for (const auto &item : you.inv)
795 {
796 if (item.defined())
797 {
798 // adds up number of each class in invent.
799 inv_class2[item.base_type]++;
800 inv_count++;
801 }
802 }
803
804 if (!inv_count)
805 {
806 text += "You aren't carrying anything.";
807 text += "\n";
808 }
809 else
810 {
811 text += "Inventory:\n\n";
812
813 for (int obj = 0; obj < NUM_OBJECT_CLASSES; obj++)
814 {
815 i = inv_order[obj];
816
817 if (inv_class2[i] == 0)
818 continue;
819
820 text += item_class_name(i);
821 text += "\n";
822
823 for (const auto &item : you.inv)
824 {
825 if (!item.defined() || item.base_type != i)
826 continue;
827
828 text += " ";
829 text += item.name(DESC_INVENTORY_EQUIP);
830
831 inv_count--;
832
833 if (origin_describable(item) && _dump_item_origin(item))
834 text += "\n" " (" + origin_desc(item) + ")";
835
836 if (is_dumpable_artefact(item))
837 text += chardump_desc(item);
838 else
839 text += "\n";
840 }
841 }
842 }
843 text += "\n\n";
844 }
845
_sdump_skills(dump_params & par)846 static void _sdump_skills(dump_params &par)
847 {
848 string &text(par.text);
849
850 text += " Skills:\n";
851
852 dump_skills(text);
853 text += "\n";
854 text += "\n";
855 }
856
spell_type_shortname(spschool spell_class,bool slash)857 static string spell_type_shortname(spschool spell_class, bool slash)
858 {
859 string ret;
860
861 if (slash)
862 ret = "/";
863
864 ret += spelltype_short_name(spell_class);
865
866 return ret;
867 }
868
_sdump_spells(dump_params & par)869 static void _sdump_spells(dump_params &par)
870 {
871 string &text(par.text);
872
873 int spell_levels = player_spell_levels();
874
875 string verb = par.se? "had" : "have";
876
877 if (spell_levels == 1)
878 text += "You " + verb + " one spell level left.";
879 else if (spell_levels == 0)
880 {
881 verb = par.se? "couldn't" : "cannot";
882
883 text += "You " + verb + " memorise any spells.";
884 }
885 else
886 {
887 if (par.se)
888 text += "You had ";
889 else
890 text += "You have ";
891 text += make_stringf("%d spell levels left.", spell_levels);
892 }
893
894 text += "\n";
895
896 if (!you.spell_no)
897 {
898 verb = par.se? "didn't" : "don't";
899
900 text += "You " + verb + " know any spells.\n";
901 }
902 else
903 {
904 verb = par.se? "knew" : "know";
905
906 text += "You " + verb + " the following spells:\n\n";
907
908 text += " Your Spells Type Power Damage Failure Level" "\n";
909
910 for (int j = 0; j < 52; j++)
911 {
912 const char letter = index_to_letter(j);
913 const spell_type spell = get_spell_by_letter(letter);
914
915 if (spell != SPELL_NO_SPELL)
916 {
917 string spell_line;
918
919 spell_line += letter;
920 spell_line += " - ";
921 spell_line += spell_title(spell);
922
923 spell_line = chop_string(spell_line, 24);
924 spell_line += " ";
925
926 bool already = false;
927
928 for (const auto bit : spschools_type::range())
929 {
930 if (spell_typematch(spell, bit))
931 {
932 spell_line += spell_type_shortname(bit, already);
933 already = true;
934 }
935 }
936
937 spell_line = chop_string(spell_line, 41);
938
939 spell_line += spell_power_string(spell);
940
941 spell_line = chop_string(spell_line, 52);
942
943 const string spell_damage = spell_damage_string(spell);
944 spell_line += spell_damage.length() ? spell_damage : "N/A";
945
946 spell_line = chop_string(spell_line, 62);
947
948 spell_line += failure_rate_to_string(raw_spell_fail(spell));
949
950 spell_line = chop_string(spell_line, 74);
951
952 spell_line += make_stringf("%d", spell_difficulty(spell));
953
954 spell_line += "\n";
955
956 text += spell_line;
957 }
958 }
959 text += "\n";
960 }
961
962 if (!you.spell_library.count())
963 {
964 verb = par.se ? "was" : "is";
965 text += "Your spell library " + verb + " empty.\n\n";
966 }
967 else
968 {
969 verb = par.se? "contained" : "contains";
970 text += "Your spell library " + verb + " the following spells:\n\n";
971 text += " Spells Type Power Damage Failure Level" "\n";
972
973 auto const library = get_sorted_spell_list(true, false);
974
975 for (const spell_type spell : library)
976 {
977 const bool memorisable = you_can_memorise(spell);
978
979 string spell_line;
980
981 spell_line += ' ';
982 spell_line += spell_title(spell);
983
984 spell_line = chop_string(spell_line, 24);
985 spell_line += " ";
986
987 bool already = false;
988
989 for (const auto bit : spschools_type::range())
990 {
991 if (spell_typematch(spell, bit))
992 {
993 spell_line += spell_type_shortname(bit, already);
994 already = true;
995 }
996 }
997
998 spell_line = chop_string(spell_line, 41);
999
1000 if (memorisable)
1001 spell_line += spell_power_string(spell);
1002 else
1003 spell_line += "Unusable";
1004
1005 spell_line = chop_string(spell_line, 52);
1006
1007 const string spell_damage = spell_damage_string(spell);
1008 spell_line += spell_damage.length() ? spell_damage : "N/A";
1009
1010 spell_line = chop_string(spell_line, 62);
1011
1012 if (memorisable)
1013 spell_line += failure_rate_to_string(raw_spell_fail(spell));
1014 else
1015 spell_line += "N/A";
1016
1017 spell_line = chop_string(spell_line, 74);
1018
1019 spell_line += make_stringf("%d", spell_difficulty(spell));
1020
1021 spell_line += "\n";
1022
1023 text += spell_line;
1024 }
1025 text += "\n\n";
1026 }
1027 }
1028
_sdump_kills(dump_params & par)1029 static void _sdump_kills(dump_params &par)
1030 {
1031 par.text += you.kills.kill_info();
1032 par.text += "\n";
1033 }
1034
_sdump_kills_place_info(const PlaceInfo place_info,string name="")1035 static string _sdump_kills_place_info(const PlaceInfo place_info, string name = "")
1036 {
1037 string out;
1038
1039 if (name.empty())
1040 name = place_info.short_name();
1041
1042 unsigned int global_total_kills = 0;
1043 for (int i = 0; i < KC_NCATEGORIES; i++)
1044 global_total_kills += you.global_info.mon_kill_num[i];
1045
1046 unsigned int total_kills = 0;
1047 for (int i = 0; i < KC_NCATEGORIES; i++)
1048 total_kills += place_info.mon_kill_num[i];
1049
1050 // Skip places where nothing was killed.
1051 if (total_kills == 0)
1052 return "";
1053
1054 float a, b, c, d, e, f;
1055
1056 a = TO_PERCENT(total_kills, global_total_kills);
1057 b = TO_PERCENT(place_info.mon_kill_num[KC_YOU],
1058 you.global_info.mon_kill_num[KC_YOU]);
1059 c = TO_PERCENT(place_info.mon_kill_num[KC_FRIENDLY],
1060 you.global_info.mon_kill_num[KC_FRIENDLY]);
1061 d = TO_PERCENT(place_info.mon_kill_num[KC_OTHER],
1062 you.global_info.mon_kill_num[KC_OTHER]);
1063 e = TO_PERCENT(place_info.mon_kill_exp,
1064 you.global_info.mon_kill_exp);
1065
1066 f = float(place_info.mon_kill_exp) / place_info.levels_seen;
1067
1068 out =
1069 make_stringf("%14s | %5.1f | %5.1f | %5.1f | %5.1f | %5.1f |"
1070 " %13.1f\n",
1071 name.c_str(), a, b, c , d, e, f);
1072
1073 return _denanify(out);
1074 }
1075
_sdump_kills_by_place(dump_params & par)1076 static void _sdump_kills_by_place(dump_params &par)
1077 {
1078 string &text(par.text);
1079
1080 const vector<PlaceInfo> all_visited = you.get_all_place_info(true);
1081
1082 string result = "";
1083
1084 string header =
1085 "Table legend:\n"
1086 " A = Kills in this place as a percentage of kills in entire the game.\n"
1087 " B = Kills by you in this place as a percentage of kills by you in\n"
1088 " the entire game.\n"
1089 " C = Kills by friends in this place as a percentage of kills by\n"
1090 " friends in the entire game.\n"
1091 " D = Other kills in this place as a percentage of other kills in the\n"
1092 " entire game.\n"
1093 " E = Experience gained in this place as a percentage of experience\n"
1094 " gained in the entire game.\n"
1095 " F = Experience gained in this place divided by the number of levels of\n"
1096 " this place that you have seen.\n\n";
1097
1098 header += " ";
1099 header += " A B C D E F\n";
1100 header += " ";
1101 header += "+-------+-------+-------+-------+-------+----------------------\n";
1102
1103 string footer = " ";
1104 footer += "+-------+-------+-------+-------+-------+----------------------\n";
1105
1106 result += _sdump_kills_place_info(you.global_info, "Total");
1107
1108 for (const PlaceInfo &pi : all_visited)
1109 result += _sdump_kills_place_info(pi);
1110
1111 if (!result.empty())
1112 text += header + result + footer + "\n";
1113 }
1114
_sdump_overview(dump_params & par)1115 static void _sdump_overview(dump_params &par)
1116 {
1117 string overview =
1118 formatted_string::parse_string(overview_description_string(false));
1119 trim_string(overview);
1120 linebreak_string(overview, 80);
1121 par.text += overview;
1122 par.text += "\n\n";
1123 }
1124
_sdump_hiscore(dump_params & par)1125 static void _sdump_hiscore(dump_params &par)
1126 {
1127 if (!par.se)
1128 return;
1129
1130 string hiscore = hiscores_format_single_long(*(par.se), true);
1131 trim_string(hiscore);
1132 par.text += hiscore;
1133 par.text += "\n\n";
1134 }
1135
_sdump_monster_list(dump_params & par)1136 static void _sdump_monster_list(dump_params &par)
1137 {
1138 string monlist = mpr_monster_list(par.se);
1139 trim_string(monlist);
1140 while (!monlist.empty())
1141 par.text += wordwrap_line(monlist, 80) + "\n";
1142 par.text += "\n";
1143 }
1144
_sdump_vault_list(dump_params & par)1145 static void _sdump_vault_list(dump_params &par)
1146 {
1147 if (par.full_id || par.se
1148 #ifdef WIZARD
1149 || you.wizard || you.suppress_wizard
1150 #endif
1151 )
1152 {
1153 par.text += "Levels and vault maps discovered:\n";
1154 par.text += dump_vault_maps();
1155 par.text += "\n";
1156 }
1157 }
1158
_sort_by_first(pair<int,FixedVector<int,28>> a,pair<int,FixedVector<int,28>> b)1159 static bool _sort_by_first(pair<int, FixedVector<int, 28> > a,
1160 pair<int, FixedVector<int, 28> > b)
1161 {
1162 for (int i = 0; i < 27; i++)
1163 {
1164 if (a.second[i] > b.second[i])
1165 return true;
1166 else if (a.second[i] < b.second[i])
1167 return false;
1168 }
1169 return false;
1170 }
1171
_count_action(caction_type type,int subtype)1172 static void _count_action(caction_type type, int subtype)
1173 {
1174 pair<caction_type, int> pair(type, subtype);
1175 if (!you.action_count.count(pair))
1176 you.action_count[pair].init(0);
1177 you.action_count[pair][you.experience_level - 1]++;
1178 }
1179
1180 /**
1181 * The alternate type is stored in the higher bytes.
1182 **/
count_action(caction_type type,int subtype,int auxtype)1183 void count_action(caction_type type, int subtype, int auxtype)
1184 {
1185 ASSERT_RANGE(subtype, -32768, 32768);
1186 ASSERT_RANGE(auxtype, -32768, 32768);
1187 _count_action(type, caction_compound(subtype, auxtype));
1188 }
1189
caction_compound(int subtype,int auxtype)1190 int caction_compound(int subtype, int auxtype)
1191 {
1192 ASSERT_RANGE(subtype, -32768, 32768);
1193 ASSERT_RANGE(auxtype, -32768, 32768);
1194 return (auxtype << 16) | (subtype & 0xFFFF);
1195 }
1196
1197 /**
1198 * .first is the subtype; .second is the auxtype (-1 if none).
1199 **/
caction_extract_types(int compound_subtype)1200 pair<int, int> caction_extract_types(int compound_subtype)
1201 {
1202 return make_pair(int16_t(compound_subtype),
1203 int16_t(compound_subtype >> 16));
1204 }
1205
_describe_action(caction_type type)1206 static string _describe_action(caction_type type)
1207 {
1208 switch (type)
1209 {
1210 case CACT_MELEE:
1211 return "Melee";
1212 case CACT_FIRE:
1213 return " Fire";
1214 case CACT_THROW:
1215 return "Throw";
1216 case CACT_ARMOUR:
1217 return "Armor"; // "Armour" is too long
1218 case CACT_BLOCK:
1219 return "Block";
1220 case CACT_DODGE:
1221 return "Dodge";
1222 case CACT_CAST:
1223 return " Cast";
1224 case CACT_INVOKE:
1225 return "Invok";
1226 case CACT_ABIL:
1227 return " Abil";
1228 case CACT_EVOKE:
1229 return "Evoke";
1230 case CACT_USE:
1231 return " Use";
1232 case CACT_STAB:
1233 return " Stab";
1234 #if TAG_MAJOR_VERSION == 34
1235 case CACT_EAT:
1236 return " Eat";
1237 #endif
1238 case CACT_RIPOSTE:
1239 return "Rpst.";
1240 default:
1241 return "Error";
1242 }
1243 }
1244
1245 static const char* _stab_names[] =
1246 {
1247 "Normal",
1248 "Distracted",
1249 "Confused",
1250 "Fleeing",
1251 "Invisible",
1252 "Held in net/web",
1253 "Petrifying", // could be nice to combine the two
1254 "Petrified",
1255 "Paralysed",
1256 "Sleeping",
1257 "Betrayed ally",
1258 };
1259
1260 static const char* _aux_attack_names[1 + UNAT_LAST_ATTACK] =
1261 {
1262 "No attack",
1263 "Constrict",
1264 "Kick",
1265 "Headbutt",
1266 "Peck",
1267 "Tailslap",
1268 "Punch",
1269 "Bite",
1270 "Pseudopods",
1271 "Tentacles",
1272 };
1273
_describe_action_subtype(caction_type type,int compound_subtype)1274 static string _describe_action_subtype(caction_type type, int compound_subtype)
1275 {
1276 pair<int, int> types = caction_extract_types(compound_subtype);
1277 int subtype = types.first;
1278 int auxtype = types.second;
1279
1280 switch (type)
1281 {
1282 case CACT_THROW:
1283 {
1284 if (auxtype == OBJ_MISSILES)
1285 return uppercase_first(item_base_name(OBJ_MISSILES, subtype));
1286 else
1287 return "Other";
1288 }
1289 case CACT_MELEE:
1290 case CACT_FIRE:
1291 case CACT_RIPOSTE:
1292 if (subtype == -1)
1293 {
1294 if (auxtype == -1)
1295 return "Unarmed";
1296 else
1297 {
1298 ASSERT_RANGE(auxtype, 0, NUM_UNARMED_ATTACKS);
1299 return _aux_attack_names[auxtype];
1300 }
1301 }
1302 else if (subtype >= UNRAND_START)
1303 {
1304 // Paranoia: an artefact may lose its specialness.
1305 const char *tn = get_unrand_entry(subtype)->type_name;
1306 if (tn)
1307 return uppercase_first(tn);
1308 subtype = get_unrand_entry(subtype)->sub_type;
1309 }
1310 return uppercase_first(item_base_name(OBJ_WEAPONS, subtype));
1311 case CACT_ARMOUR:
1312 return (subtype == -1) ? "Skin"
1313 : uppercase_first(item_base_name(OBJ_ARMOUR, subtype));
1314 case CACT_BLOCK:
1315 {
1316 if (subtype > -1)
1317 return uppercase_first(item_base_name(OBJ_ARMOUR, subtype));
1318 switch (auxtype)
1319 {
1320 case BLOCK_OTHER:
1321 return "Other"; // non-shield block
1322 case BLOCK_REFLECT:
1323 return "Reflection";
1324 default:
1325 return "Error";
1326 }
1327 }
1328 case CACT_DODGE:
1329 {
1330 switch ((dodge_type)subtype)
1331 {
1332 case DODGE_EVASION:
1333 return "Dodged";
1334 case DODGE_REPEL:
1335 return "Repelled";
1336 default:
1337 return "Error";
1338 }
1339 }
1340 case CACT_CAST:
1341 return spell_title((spell_type)subtype);
1342 case CACT_INVOKE:
1343 case CACT_ABIL:
1344 return ability_name((ability_type)subtype);
1345 case CACT_EVOKE:
1346 if (subtype >= UNRAND_START && subtype <= UNRAND_LAST)
1347 return uppercase_first(get_unrand_entry(subtype)->name);
1348
1349 if (auxtype > -1)
1350 {
1351 item_def dummy;
1352 dummy.base_type = (object_class_type)(auxtype);
1353 dummy.sub_type = subtype;
1354 dummy.quantity = 1;
1355 return uppercase_first(dummy.name(DESC_DBNAME, true));
1356 }
1357
1358 switch ((evoc_type)subtype)
1359 {
1360 case EVOC_WAND:
1361 return "Wand";
1362 #if TAG_MAJOR_VERSION == 34
1363 case EVOC_ROD:
1364 return "Rod";
1365 case EVOC_DECK:
1366 return "Deck";
1367 case EVOC_MISC:
1368 return "Miscellaneous";
1369 case EVOC_BUGGY_TOME:
1370 return "tome";
1371 #endif
1372 default:
1373 return "Error";
1374 }
1375 case CACT_USE:
1376 return uppercase_first(base_type_string((object_class_type)subtype));
1377 case CACT_STAB:
1378 COMPILE_CHECK(ARRAYSZ(_stab_names) == NUM_STABS);
1379 ASSERT_RANGE(subtype, 1, NUM_STABS);
1380 return _stab_names[subtype];
1381 #if TAG_MAJOR_VERSION == 34
1382 case CACT_EAT:
1383 return "Removed food";
1384 #endif
1385 default:
1386 return "Error";
1387 }
1388 }
1389
_sdump_action_counts(dump_params & par)1390 static void _sdump_action_counts(dump_params &par)
1391 {
1392 if (you.action_count.empty())
1393 return;
1394 int max_lt = (min<int>(you.max_level, 27) - 1) / 3;
1395
1396 // Don't show both a total and 1..3 when there's only one tier.
1397 if (max_lt)
1398 max_lt++;
1399
1400 par.text += make_stringf("%-24s", "Action");
1401 for (int lt = 0; lt < max_lt; lt++)
1402 par.text += make_stringf(" | %2d-%2d", lt * 3 + 1, lt * 3 + 3);
1403 par.text += make_stringf(" || %5s", "total");
1404 par.text += "\n-------------------------";
1405 for (int lt = 0; lt < max_lt; lt++)
1406 par.text += "+-------";
1407 par.text += "++-------\n";
1408
1409 for (int cact = 0; cact < NUM_CACTIONS; cact++)
1410 {
1411 vector<pair<int, FixedVector<int, 28> > > action_vec;
1412 for (const auto &entry : you.action_count)
1413 {
1414 if (entry.first.first != cact)
1415 continue;
1416 FixedVector<int, 28> v;
1417 v[27] = 0;
1418 for (int i = 0; i < 27; i++)
1419 {
1420 v[i] = entry.second[i];
1421 v[27] += v[i];
1422 }
1423 action_vec.emplace_back(entry.first.second, v);
1424 }
1425 sort(action_vec.begin(), action_vec.end(), _sort_by_first);
1426
1427 for (auto ac = action_vec.begin(); ac != action_vec.end(); ++ac)
1428 {
1429 if (ac == action_vec.begin())
1430 {
1431 par.text += _describe_action(caction_type(cact));
1432 par.text += ": ";
1433 }
1434 else
1435 par.text += " ";
1436 par.text += chop_string(_describe_action_subtype(caction_type(cact), ac->first), 17);
1437 for (int lt = 0; lt < max_lt; lt++)
1438 {
1439 int ltotal = 0;
1440 for (int i = lt * 3; i < lt * 3 + 3; i++)
1441 ltotal += ac->second[i];
1442 if (ltotal)
1443 par.text += make_stringf(" |%6d", ltotal);
1444 else
1445 par.text += " | ";
1446 }
1447 par.text += make_stringf(" ||%6d", ac->second[27]);
1448 par.text += "\n";
1449 }
1450 }
1451 par.text += "\n";
1452 }
1453
_sdump_skill_gains(dump_params & par)1454 static void _sdump_skill_gains(dump_params &par)
1455 {
1456 typedef map<int, int> XlToSkillLevelMap;
1457 map<skill_type, XlToSkillLevelMap> skill_gains;
1458 vector<skill_type> skill_order;
1459 int xl = 0;
1460 int max_xl = 0;
1461 for (const Note ¬e : note_list)
1462 {
1463 if (note.type == NOTE_XP_LEVEL_CHANGE)
1464 xl = note.first;
1465 else if (note.type == NOTE_GAIN_SKILL || note.type == NOTE_LOSE_SKILL)
1466 {
1467 skill_type skill = static_cast<skill_type>(note.first);
1468 int skill_level = note.second;
1469 if (skill_gains.find(skill) == skill_gains.end())
1470 skill_order.push_back(skill);
1471 skill_gains[skill][xl] = skill_level;
1472 max_xl = max(max_xl, xl);
1473 }
1474 }
1475
1476 if (skill_order.empty())
1477 return;
1478
1479 for (int i = 0; i < NUM_SKILLS; i++)
1480 {
1481 skill_type skill = static_cast<skill_type>(i);
1482 if (you.skill(skill, 10, true) > 0
1483 && skill_gains.find(skill) == skill_gains.end())
1484 {
1485 skill_order.push_back(skill);
1486 }
1487 }
1488
1489 par.text += "Skill XL: |";
1490 for (xl = 1; xl <= max_xl; xl++)
1491 par.text += make_stringf(" %2d", xl);
1492 par.text += " |\n";
1493 par.text += "---------------+";
1494 for (xl = 1; xl <= max_xl; xl++)
1495 par.text += "---";
1496 par.text += "-+-----\n";
1497
1498 for (skill_type skill : skill_order)
1499 {
1500 par.text += make_stringf("%-14s |", skill_name(skill));
1501 const XlToSkillLevelMap &gains = skill_gains[skill];
1502 for (xl = 1; xl <= max_xl; xl++)
1503 {
1504 auto it = gains.find(xl);
1505 if (it != gains.end())
1506 par.text += make_stringf(" %2d", it->second);
1507 else
1508 par.text += " ";
1509 }
1510 par.text += make_stringf(" | %4.1f\n",
1511 you.skill(skill, 10, true) * 0.1);
1512 }
1513 par.text += "\n";
1514 }
1515
_sdump_mutations(dump_params & par)1516 static void _sdump_mutations(dump_params &par)
1517 {
1518 string &text(par.text);
1519
1520 if (you.how_mutated(true, false))
1521 {
1522 text += "\n";
1523 text += (formatted_string::parse_string(describe_mutations(false)));
1524 text += "\n\n";
1525 }
1526 }
1527
morgue_directory()1528 string morgue_directory()
1529 {
1530 string dir = (!Options.morgue_dir.empty() ? Options.morgue_dir :
1531 !SysEnv.crawl_dir.empty() ? SysEnv.crawl_dir
1532 : "");
1533
1534 if (!dir.empty() && dir[dir.length() - 1] != FILE_SEPARATOR)
1535 dir += FILE_SEPARATOR;
1536
1537 return dir;
1538 }
1539
dump_map(FILE * fp,bool debug,bool dist,bool log)1540 void dump_map(FILE *fp, bool debug, bool dist, bool log)
1541 {
1542 if (debug)
1543 {
1544 #ifdef COLOURED_DUMPS
1545 // Usage: make EXTERNAL_DEFINES="-DCOLOURED_DUMPS"
1546 // To read the dumps, cat them or use less -R.
1547 // ansi2html can be used to make html.
1548
1549 fprintf(fp, "Vaults used:\n");
1550 for (size_t i = 0; i < env.level_vaults.size(); ++i)
1551 {
1552 const vault_placement &vp(*env.level_vaults[i]);
1553 fprintf(fp, " \e[3%dm%s\e[0m at (%d,%d) size (%d,%d)\n",
1554 6 - (int)i % 6, vp.map.name.c_str(),
1555 vp.pos.x, vp.pos.y, vp.size.x, vp.size.y);
1556 }
1557 fprintf(fp, " (bright = stacked, \e[37;1mwhite\e[0m = not in level_map_ids)\n");
1558 size_t last_nv = 0;
1559 int last_v = 0;
1560 #endif
1561 // Write the whole map out without checking for mappedness. Handy
1562 // for debugging level-generation issues.
1563 for (int y = 0; y < GYM; ++y)
1564 {
1565 for (int x = 0; x < GXM; ++x)
1566 {
1567 #ifdef COLOURED_DUMPS
1568 size_t nv = 0;
1569 for (auto &vault : env.level_vaults)
1570 if (vault->map.in_map(coord_def(x, y) - vault->pos))
1571 nv++;
1572
1573 int v = env.level_map_ids[x][y];
1574 if (v == INVALID_MAP_INDEX)
1575 v = -1;
1576 if (nv != last_nv || v != last_v)
1577 {
1578 if (nv)
1579 fprintf(fp, "\e[%d;3%dm", nv != 1, 6 - v % 6);
1580 else
1581 fprintf(fp, "\e[0m");
1582 last_nv = nv;
1583 last_v = v;
1584 }
1585 #endif
1586 if (dist && you.pos() == coord_def(x, y))
1587 fputc('@', fp);
1588 else if (testbits(env.pgrid[x][y], FPROP_HIGHLIGHT))
1589 fputc('?', fp);
1590 else if (dist && env.grid[x][y] == DNGN_FLOOR
1591 && travel_point_distance[x][y] > 0
1592 && travel_point_distance[x][y] < 10)
1593 {
1594 fputc('0' + travel_point_distance[x][y], fp);
1595 }
1596 else if (env.grid[x][y] >= NUM_FEATURES)
1597 fputc('!', fp);
1598 else
1599 {
1600 fputs(OUTS(stringize_glyph(
1601 get_feature_def(env.grid[x][y]).symbol())), fp);
1602 }
1603 }
1604 fputc('\n', fp);
1605 #ifdef COLOURED_DUMPS
1606 last_v = 0; // force a colour code, because of less+libvte
1607 #endif
1608 }
1609 #ifdef COLOURED_DUMPS
1610 fprintf(fp, "\e[0m");
1611 #endif
1612 }
1613 else
1614 {
1615 int min_x = GXM-1, max_x = 0, min_y = GYM-1, max_y = 0;
1616
1617 for (int i = X_BOUND_1; i <= X_BOUND_2; i++)
1618 for (int j = Y_BOUND_1; j <= Y_BOUND_2; j++)
1619 if (env.map_knowledge[i][j].known())
1620 {
1621 if (i > max_x) max_x = i;
1622 if (i < min_x) min_x = i;
1623 if (j > max_y) max_y = j;
1624 if (j < min_y) min_y = j;
1625 }
1626
1627 for (int y = min_y; y <= max_y; ++y)
1628 {
1629 for (int x = min_x; x <= max_x; ++x)
1630 {
1631 fputs(OUTS(stringize_glyph(
1632 get_cell_glyph(coord_def(x, y)).ch)), fp);
1633 }
1634
1635 fputc('\n', fp);
1636 }
1637 }
1638
1639 // for debug use in scripts, e.g. placement.lua
1640 if (log)
1641 {
1642 string the_log = get_last_messages(NUM_STORED_MESSAGES, true);
1643 fprintf(fp, "\n%s", the_log.c_str());
1644 }
1645 }
1646
dump_map(const char * fname,bool debug,bool dist,bool log)1647 void dump_map(const char* fname, bool debug, bool dist, bool log)
1648 {
1649 FILE* fp = fopen_replace(fname);
1650 if (!fp)
1651 return;
1652
1653 dump_map(fp, debug, dist, log);
1654
1655 fclose(fp);
1656 }
1657
_write_dump(const string & fname,const dump_params & par,bool quiet)1658 static bool _write_dump(const string &fname, const dump_params &par, bool quiet)
1659 {
1660 bool succeeded = false;
1661
1662 string file_name = morgue_directory();
1663
1664 file_name += strip_filename_unsafe_chars(fname);
1665
1666 StashTrack.update_corpses();
1667
1668 string stash_file_name;
1669 stash_file_name = file_name;
1670 stash_file_name += ".lst";
1671 StashTrack.dump(stash_file_name.c_str(), par.full_id);
1672
1673 file_name += ".txt";
1674 FILE *handle = fopen_replace(file_name.c_str());
1675
1676 dprf("File name: %s", file_name.c_str());
1677
1678 if (handle != nullptr)
1679 {
1680 fputs(OUTS(par.text), handle);
1681 fclose(handle);
1682 succeeded = true;
1683 if (!quiet)
1684 #ifdef DGAMELAUNCH
1685 mpr("Char dumped successfully.");
1686 #else
1687 mprf("Char dumped to '%s'.", file_name.c_str());
1688 #endif
1689 }
1690 else
1691 mprf(MSGCH_ERROR, "Error opening file '%s'", file_name.c_str());
1692
1693 return succeeded;
1694 }
1695
display_notes()1696 void display_notes()
1697 {
1698 formatted_scroller scr(FS_START_AT_END | FS_PREWRAPPED_TEXT);
1699 scr.set_more();
1700 scr.set_tag("notes");
1701 scr.add_raw_text("Turn | Place | Note\n");
1702 for (const Note ¬e : note_list)
1703 {
1704 if (note.hidden())
1705 continue;
1706 string prefix = note.describe(true, true, false);
1707 string suffix = note.describe(false, false, true);
1708 if (suffix.empty())
1709 continue;
1710 int spaceleft = 80 - prefix.length() - 1; // Use 100 cols
1711 if (spaceleft <= 0)
1712 return;
1713
1714 linebreak_string(suffix, spaceleft);
1715 vector<string> parts = split_string("\n", suffix);
1716 if (parts.empty()) // Disregard pure-whitespace notes.
1717 continue;
1718
1719 scr.add_raw_text(prefix + parts[0] + "\n");
1720 for (unsigned int j = 1; j < parts.size(); ++j)
1721 scr.add_raw_text(string(prefix.length()-2, ' ') + string("| ") + parts[j] + "\n");
1722 }
1723 scr.show();
1724 }
1725
display_char_dump()1726 void display_char_dump()
1727 {
1728 formatted_scroller scr(FS_PREWRAPPED_TEXT);
1729 scr.add_raw_text(_get_dump().text, false);
1730 scr.set_more();
1731 scr.set_tag("dump");
1732 scr.show();
1733 }
1734
1735 #ifdef DGL_WHEREIS
1736 ///////////////////////////////////////////////////////////////////////////
1737 // whereis player
whereis_record(const char * status)1738 void whereis_record(const char *status)
1739 {
1740 const string file_name = morgue_directory()
1741 + strip_filename_unsafe_chars(you.your_name)
1742 + string(".where");
1743
1744 if (FILE *handle = fopen_replace(file_name.c_str()))
1745 {
1746 // no need to bother with supporting ancient charsets for DGL
1747 fprintf(handle, "%s:status=%s\n",
1748 xlog_status_line().c_str(),
1749 status? status : "");
1750 fclose(handle);
1751 }
1752 }
1753 #endif
1754
1755 ///////////////////////////////////////////////////////////////////////////////
1756 // Turn timestamps
1757 //
1758 // For DGL installs, write a timestamp at regular intervals into a file in
1759 // the morgue directory. The timestamp file is named
1760 // "timestamp-<player>-<starttime>.ts". All timestamps are standard Unix
1761 // time_t, but currently only the low 4 bytes are saved even on systems
1762 // with 64-bit time_t.
1763 //
1764 // Timestamp files are append only, and Crawl will check and handle cases
1765 // where a previous Crawl process crashed at a higher turn count on the same
1766 // game.
1767 //
1768 // Having timestamps associated with the game allows for much easier seeking
1769 // within Crawl ttyrecs by external tools such as FooTV.
1770
1771 #ifdef DGL_TURN_TIMESTAMPS
1772
1773 #include "syscalls.h"
1774 #include <sys/stat.h>
1775
1776 // File-format version for timestamp files. Crawl will never append to a
1777 const uint32_t DGL_TIMESTAMP_VERSION = 1;
1778 const int VERSION_SIZE = sizeof(DGL_TIMESTAMP_VERSION);
1779 const int TIMESTAMP_SIZE = sizeof(uint32_t);
1780
1781 // Returns the name of the timestamp file based on the morgue_dir,
1782 // character name and the game start time.
_dgl_timestamp_filename()1783 static string _dgl_timestamp_filename()
1784 {
1785 const string filename = "timestamp-" + you.your_name + "-"
1786 + make_file_time(you.birth_time);
1787 return morgue_directory() + strip_filename_unsafe_chars(filename) + ".ts";
1788 }
1789
1790 // Returns true if the given file exists and is not a timestamp file
1791 // of a known version or truncated timestamp file.
_dgl_unknown_timestamp_file(const string & filename)1792 static bool _dgl_unknown_timestamp_file(const string &filename)
1793 {
1794 if (FILE *inh = fopen_u(filename.c_str(), "rb"))
1795 {
1796 reader r(inh);
1797 r.set_safe_read(true);
1798 try
1799 {
1800 const uint32_t file_version = unmarshallInt(r);
1801 fclose(inh);
1802 return file_version != DGL_TIMESTAMP_VERSION;
1803 }
1804 catch (short_read_exception &e)
1805 {
1806 // Empty file, or <4 bytes: remove file and use it.
1807 fclose(inh);
1808 // True (don't use) if we couldn't remove it, false if we could.
1809 return unlink_u(filename.c_str()) != 0;
1810 }
1811 }
1812 return false;
1813 }
1814
1815 // Returns a filehandle to use to write turn timestamps, nullptr if
1816 // timestamps should not be written.
_dgl_timestamp_filehandle()1817 static FILE *_dgl_timestamp_filehandle()
1818 {
1819 static FILE *timestamp_file = nullptr;
1820 static bool opened_file = false;
1821 if (!opened_file)
1822 {
1823 opened_file = true;
1824
1825 const string filename = _dgl_timestamp_filename();
1826 // First check if there's already a timestamp file. If it exists
1827 // but has a different version, we cannot safely modify it, so bail.
1828 if (!_dgl_unknown_timestamp_file(filename))
1829 timestamp_file = fopen_u(filename.c_str(), "ab");
1830 }
1831 return timestamp_file;
1832 }
1833
1834 // Records a timestamp in the .ts file at the given offset. If no timestamp
1835 // file exists, a new file will be created.
_dgl_record_timestamp(unsigned long file_offset,time_t time)1836 static void _dgl_record_timestamp(unsigned long file_offset, time_t time)
1837 {
1838 static bool timestamp_first_write = true;
1839 if (FILE *ftimestamp = _dgl_timestamp_filehandle())
1840 {
1841 writer w(_dgl_timestamp_filename(), ftimestamp, true);
1842 if (timestamp_first_write)
1843 {
1844 unsigned long ts_size = file_size(ftimestamp);
1845 if (!ts_size)
1846 {
1847 marshallInt(w, DGL_TIMESTAMP_VERSION);
1848 ts_size += sizeof(DGL_TIMESTAMP_VERSION);
1849 }
1850
1851 // It's possible that the file we want to write is already
1852 // larger than the offset we expect if the game previously
1853 // crashed. When the game crashes, turn count is
1854 // effectively rewound to the point of the last save. In
1855 // such cases, we should not add timestamps until we reach
1856 // the correct turn count again.
1857 if (ts_size && ts_size > file_offset)
1858 return;
1859
1860 if (file_offset > ts_size)
1861 {
1862 const int backlog =
1863 (file_offset - ts_size) / TIMESTAMP_SIZE;
1864 for (int i = 0; i < backlog; ++i)
1865 marshallInt(w, 0);
1866 }
1867
1868 timestamp_first_write = false;
1869 }
1870 fseek(ftimestamp, 0, SEEK_END);
1871 // [ds] FIXME: Eventually switch to 8 byte timestamps.
1872 marshallInt(w, static_cast<uint32_t>(time));
1873 fflush(ftimestamp);
1874 }
1875 }
1876
1877 // Record timestamps every so many turns:
1878 const int TIMESTAMP_TURN_INTERVAL = 100;
1879 // Stop recording timestamps after this turncount.
1880 const int TIMESTAMP_TURN_MAX = 500000;
_dgl_record_timestamp(int turn)1881 static void _dgl_record_timestamp(int turn)
1882 {
1883 if (turn && turn < TIMESTAMP_TURN_MAX && !(turn % TIMESTAMP_TURN_INTERVAL))
1884 {
1885 const time_t now = time(nullptr);
1886 const unsigned long offset =
1887 (VERSION_SIZE +
1888 (turn / TIMESTAMP_TURN_INTERVAL - 1) * TIMESTAMP_SIZE);
1889 _dgl_record_timestamp(offset, now);
1890 }
1891 }
1892
1893 #endif
1894
1895 // Records a timestamp for the current player turn if appropriate.
record_turn_timestamp()1896 void record_turn_timestamp()
1897 {
1898 #ifdef DGL_TURN_TIMESTAMPS
1899 if (crawl_state.need_save)
1900 _dgl_record_timestamp(you.num_turns);
1901 #endif
1902 }
1903