Apply the new ground_level method.
[crawl.git] / crawl-ref / source / chardump.cc
blob50ab25289d0185fe909382b960715915cf141882
1 /*
2 * File: chardump.cc
3 * Summary: Dumps character info out to the morgue file.
4 * Written by: Linley Henzell
5 */
7 #include "AppHdr.h"
9 #include "chardump.h"
10 #include "clua.h"
12 #include <string>
13 #include <stdio.h>
14 #include <string.h>
15 #include <fcntl.h>
16 #include <stdlib.h>
17 #if !defined(__IBMCPP__) && !defined(TARGET_COMPILER_VC)
18 #include <unistd.h>
19 #endif
20 #include <ctype.h>
22 #include "externs.h"
23 #include "options.h"
25 #include "artefact.h"
26 #include "debug.h"
27 #include "describe.h"
28 #include "dgn-overview.h"
29 #include "dungeon.h"
30 #include "files.h"
31 #include "godprayer.h"
32 #include "hiscores.h"
33 #include "initfile.h"
34 #include "itemprop.h"
35 #include "itemname.h"
36 #include "items.h"
37 #include "kills.h"
38 #include "message.h"
39 #include "menu.h"
40 #include "mutation.h"
41 #include "notes.h"
42 #include "output.h"
43 #include "place.h"
44 #include "player.h"
45 #include "religion.h"
46 #include "shopping.h"
47 #include "showsymb.h"
48 #include "skills2.h"
49 #include "spl-book.h"
50 #include "spl-cast.h"
51 #include "spl-util.h"
52 #include "stash.h"
53 #include "state.h"
54 #include "stuff.h"
55 #include "env.h"
56 #include "transform.h"
57 #include "travel.h"
58 #include "view.h"
59 #include "viewchar.h"
60 #include "xom.h"
62 struct dump_params;
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_burden(dump_params &);
69 static void _sdump_hunger(dump_params &);
70 static void _sdump_transform(dump_params &);
71 static void _sdump_visits(dump_params &);
72 static void _sdump_gold(dump_params &);
73 static void _sdump_misc(dump_params &);
74 static void _sdump_turns_by_place(dump_params &);
75 static void _sdump_notes(dump_params &);
76 static void _sdump_inventory(dump_params &);
77 static void _sdump_skills(dump_params &);
78 static void _sdump_spells(dump_params &);
79 static void _sdump_mutations(dump_params &);
80 static void _sdump_messages(dump_params &);
81 static void _sdump_screenshot(dump_params &);
82 static void _sdump_kills_by_place(dump_params &);
83 static void _sdump_kills(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_separator(dump_params &);
90 #ifdef CLUA_BINDINGS
91 static void _sdump_lua(dump_params &);
92 #endif
93 static bool write_dump(const std::string &fname, dump_params &);
95 struct dump_section_handler
97 const char *name;
98 void (*handler)(dump_params &);
101 struct dump_params
103 std::string &text;
104 std::string section;
105 bool show_prices;
106 bool full_id;
107 const scorefile_entry *se;
109 dump_params(std::string &_text, const std::string &sec = "",
110 bool prices = false, bool id = false,
111 const scorefile_entry *s = NULL)
112 : text(_text), section(sec), show_prices(prices), full_id(id),
113 se(s)
118 static dump_section_handler dump_handlers[] = {
119 { "header", _sdump_header },
120 { "stats", _sdump_stats },
121 { "location", _sdump_location },
122 { "religion", _sdump_religion },
123 { "burden", _sdump_burden },
124 { "hunger", _sdump_hunger },
125 { "transform", _sdump_transform },
126 { "visits", _sdump_visits },
127 { "gold", _sdump_gold },
128 { "misc", _sdump_misc },
129 { "turns_by_place", _sdump_turns_by_place},
130 { "notes", _sdump_notes },
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 { "overview", _sdump_overview },
140 { "hiscore", _sdump_hiscore },
141 { "monlist", _sdump_monster_list },
142 { "vaults", _sdump_vault_list },
144 // Conveniences for the .crawlrc artist.
145 { "", _sdump_newline },
146 { "-", _sdump_separator },
148 #ifdef CLUA_BINDINGS
149 { NULL, _sdump_lua }
150 #else
151 { NULL, NULL }
152 #endif
155 static void dump_section(dump_params &par)
157 for (int i = 0; ; ++i)
159 if (!dump_handlers[i].name || par.section == dump_handlers[i].name)
161 if (dump_handlers[i].handler)
162 (*dump_handlers[i].handler)(par);
163 break;
168 bool dump_char(const std::string &fname, bool show_prices, bool full_id,
169 const scorefile_entry *se)
171 // Start with enough room for 100 80 character lines.
172 std::string text;
173 text.reserve(100 * 80);
175 dump_params par(text, "", show_prices, full_id, se);
177 for (int i = 0, size = Options.dump_order.size(); i < size; ++i)
179 par.section = Options.dump_order[i];
180 dump_section(par);
183 return write_dump(fname, par);
186 static void _sdump_header(dump_params &par)
188 std::string type = crawl_state.game_type_name();
189 if (type.empty())
190 type = CRAWL;
191 else
192 type += " DCSS";
194 par.text += " " + type + " version " + Version::Long();
195 par.text += " character file.\n\n";
198 static void _sdump_stats(dump_params &par)
200 par.text += dump_overview_screen(par.full_id);
201 par.text += "\n\n";
204 static void _sdump_burden(dump_params &par)
206 std::string verb = par.se? "were" : "are";
208 switch (you.burden_state)
210 case BS_OVERLOADED:
211 par.text += "You " + verb + " overloaded with stuff.\n";
212 break;
213 case BS_ENCUMBERED:
214 par.text += "You " + verb + " encumbered.\n";
215 break;
216 default:
217 break;
221 static void _sdump_hunger(dump_params &par)
223 if (par.se)
224 par.text += "You were ";
225 else
226 par.text += "You are ";
228 par.text += hunger_level();
229 par.text += ".\n\n";
232 static void _sdump_transform(dump_params &par)
234 std::string &text(par.text);
235 if (you.form)
237 std::string verb = par.se? "were" : "are";
239 switch (you.form)
241 case TRAN_SPIDER:
242 text += "You " + verb + " in spider-form.";
243 break;
244 case TRAN_BAT:
245 text += "You " + verb + " in ";
246 if (you.species == SP_VAMPIRE)
247 text += "vampire ";
248 text += "bat-form.";
249 break;
250 case TRAN_BLADE_HANDS:
251 text += "Your " + blade_parts() + " " + verb + " blades.";
252 break;
253 case TRAN_STATUE:
254 text += "You " + verb + " a stone statue.";
255 break;
256 case TRAN_ICE_BEAST:
257 text += "You " + verb + " a creature of crystalline ice.";
258 break;
259 case TRAN_DRAGON:
260 text += "You " + verb + " a fearsome dragon!";
261 break;
262 case TRAN_LICH:
263 text += "You " + verb + " in lich-form.";
264 break;
265 case TRAN_PIG:
266 text += "You " + verb + " a filthy swine.";
267 break;
268 case TRAN_NONE:
269 break;
272 text += "\n\n";
276 static void _sdump_visits(dump_params &par)
278 std::string &text(par.text);
280 std::string have = "have ";
281 std::string seen = "seen";
282 if (par.se) // you died -> past tense
284 have = "";
285 seen = "saw";
288 std::vector<PlaceInfo> branches_visited =
289 you.get_all_place_info(true, true);
291 PlaceInfo branches_total;
292 for (unsigned int i = 0; i < branches_visited.size(); i++)
293 branches_total += branches_visited[i];
295 text += make_stringf("You %svisited %d branch",
296 have.c_str(), branches_visited.size());
297 if (branches_visited.size() != 1)
298 text += "es";
299 text += make_stringf(" of the dungeon, and %s %d of its levels.\n",
300 seen.c_str(), branches_total.levels_seen);
302 PlaceInfo place_info = you.get_place_info(LEVEL_PANDEMONIUM);
303 if (place_info.num_visits > 0)
305 text += make_stringf("You %svisited Pandemonium %d time",
306 have.c_str(), place_info.num_visits);
307 if (place_info.num_visits > 1)
308 text += "s";
309 text += make_stringf(", and %s %d of its levels.\n",
310 seen.c_str(), place_info.levels_seen);
313 place_info = you.get_place_info(LEVEL_ABYSS);
314 if (place_info.num_visits > 0)
316 text += make_stringf("You %svisited the Abyss %d time",
317 have.c_str(), place_info.num_visits);
318 if (place_info.num_visits > 1)
319 text += "s";
320 text += ".\n";
323 place_info = you.get_place_info(LEVEL_LABYRINTH);
324 if (place_info.num_visits > 0)
326 text += make_stringf("You %svisited %d Labyrinth",
327 have.c_str(), place_info.num_visits);
328 if (place_info.num_visits > 1)
329 text += "s";
330 text += ".\n";
333 place_info = you.get_place_info(LEVEL_PORTAL_VAULT);
334 if (place_info.num_visits > 0)
336 CrawlVector &vaults =
337 you.props[YOU_PORTAL_VAULT_NAMES_KEY].get_vector();
339 int num_bazaars = 0;
340 int num_zigs = 0;
341 int zig_levels = 0;
342 std::vector<std::string> misc_portals;
344 for (unsigned int i = 0; i < vaults.size(); i++)
346 std::string name = vaults[i].get_string();
348 if (name.find("Ziggurat") != std::string::npos)
350 zig_levels++;
352 if (name == "Ziggurat:1")
353 num_zigs++;
355 else if (name == "bazaar")
356 num_bazaars++;
357 else
358 misc_portals.push_back(name);
361 if (num_bazaars > 0)
363 text += make_stringf("You %svisited %d bazaar",
364 have.c_str(), num_bazaars);
366 if (num_bazaars > 1)
367 text += "s";
368 text += ".\n";
371 if (num_zigs > 0)
373 text += make_stringf("You %svisited %d Ziggurat",
374 have.c_str(), num_zigs);
375 if (num_zigs > 1)
376 text += "s";
377 text += make_stringf(", and %s %d of %s levels.\n",
378 seen.c_str(), zig_levels,
379 num_zigs > 1 ? "their" : "its");
382 if (!misc_portals.empty())
384 text += make_stringf("You %svisited %d portal chamber",
385 have.c_str(), misc_portals.size());
386 if (misc_portals.size() > 1)
387 text += "s";
388 text += ": ";
389 text += comma_separated_line(misc_portals.begin(),
390 misc_portals.end(),
391 ", ");
392 text += ".\n";
396 text += "\n";
399 static void _sdump_gold(dump_params &par)
401 std::string &text(par.text);
403 int lines = 0;
405 const char* have = "have ";
406 if (par.se) // you died -> past tense
407 have = "";
409 if (you.attribute[ATTR_GOLD_FOUND] > 0)
411 lines++;
412 text += make_stringf("You %scollected %d gold pieces.\n", have,
413 you.attribute[ATTR_GOLD_FOUND]);
416 if (you.attribute[ATTR_PURCHASES] > 0)
418 lines++;
419 text += make_stringf("You %sspent %d gold pieces at shops.\n", have,
420 you.attribute[ATTR_PURCHASES]);
423 if (you.attribute[ATTR_DONATIONS] > 0)
425 lines++;
426 text += make_stringf("You %sdonated %d gold pieces.\n", have,
427 you.attribute[ATTR_DONATIONS]);
430 if (you.attribute[ATTR_MISC_SPENDING] > 0)
432 lines++;
433 text += make_stringf("You %sused %d gold pieces for miscellaneous "
434 "purposes.\n", have,
435 you.attribute[ATTR_MISC_SPENDING]);
438 if (lines > 0)
439 text += "\n";
442 static void _sdump_misc(dump_params &par)
444 _sdump_location(par);
445 _sdump_religion(par);
446 _sdump_burden(par);
447 _sdump_hunger(par);
448 _sdump_transform(par);
449 _sdump_visits(par);
450 _sdump_gold(par);
453 #define TO_PERCENT(x, y) (100.0f * (static_cast<float>(x)) / (static_cast<float>(y)))
455 static std::string _sdump_turns_place_info(PlaceInfo place_info,
456 std::string name = "")
458 PlaceInfo gi = you.global_info;
459 std::string out;
461 if (name.empty())
462 name = place_info.short_name();
464 float a, b, c, d, e, f;
465 unsigned int non_interlevel =
466 place_info.turns_total - place_info.turns_interlevel;
467 unsigned int global_non_interlevel =
468 gi.turns_total - gi.turns_interlevel;
471 a = TO_PERCENT(place_info.turns_total, gi.turns_total);
472 b = TO_PERCENT(non_interlevel, global_non_interlevel);
473 c = TO_PERCENT(place_info.turns_interlevel, place_info.turns_total);
474 d = TO_PERCENT(place_info.turns_resting, non_interlevel);
475 e = TO_PERCENT(place_info.turns_explore, non_interlevel);
476 f = static_cast<float>(non_interlevel) /
477 static_cast<float>(place_info.levels_seen);
479 out =
480 make_stringf("%14s | %5.1f | %5.1f | %5.1f | %5.1f | %5.1f | %13.1f\n",
481 name.c_str(), a, b, c , d, e, f);
483 out = replace_all(out, " nan ", " N/A ");
485 return out;
488 static void _sdump_turns_by_place(dump_params &par)
490 std::string &text(par.text);
492 std::vector<PlaceInfo> all_visited =
493 you.get_all_place_info(true);
495 text +=
496 "Table legend:\n"
497 " A = Turns spent in this place as a percentage of turns spent in the\n"
498 " entire game.\n"
499 " B = Non-inter-level travel turns spent in this place as a percentage of\n"
500 " non-inter-level travel turns spent in the entire game.\n"
501 " C = Inter-level travel turns spent in this place as a percentage of\n"
502 " turns spent in this place.\n"
503 " D = Turns resting spent in this place as a percentage of non-inter-level\n"
504 " travel turns spent in this place.\n"
505 " E = Turns spent auto-exploring this place as a percentage of\n"
506 " non-inter-level travel turns spent in this place.\n"
507 " F = Non-inter-level travel turns spent in this place divided by the\n"
508 " number of levels of this place that you've seen.\n\n";
510 text += " ";
511 text += " A B C D E F\n";
512 text += " ";
513 text += "+-------+-------+-------+-------+-------+----------------------\n";
515 text += _sdump_turns_place_info(you.global_info, "Total");
517 for (unsigned int i = 0; i < all_visited.size(); i++)
519 PlaceInfo pi = all_visited[i];
520 text += _sdump_turns_place_info(pi);
523 text += " ";
524 text += "+-------+-------+-------+-------+-------+----------------------\n";
526 text += "\n";
529 static void _sdump_newline(dump_params &par)
531 par.text += "\n";
534 static void _sdump_separator(dump_params &par)
536 par.text += std::string(79, '-') + "\n";
539 #ifdef CLUA_BINDINGS
540 // Assume this is an arbitrary Lua function name, call the function and
541 // dump whatever it returns.
542 static void _sdump_lua(dump_params &par)
544 std::string luatext;
545 if (!clua.callfn(par.section.c_str(), ">s", &luatext)
546 && !clua.error.empty())
548 par.text += "Lua dump error: " + clua.error + "\n";
550 else
551 par.text += luatext;
553 #endif
555 //---------------------------------------------------------------
557 // munge_description
559 // word wrap to 80 characters.
560 // XXX: should be replaced by some other linewrapping function
561 // now EOL munging is gone
562 //---------------------------------------------------------------
563 std::string munge_description(const std::string & inStr)
565 std::string outStr;
567 outStr.reserve(inStr.length() + 32);
569 const int kIndent = 3;
570 int lineLen = kIndent;
572 unsigned int i = 0;
574 outStr += std::string(kIndent, ' ');
576 while (i < inStr.length())
578 const char ch = inStr[i];
580 if (ch == '\n')
582 outStr += "\n";
584 outStr += std::string(kIndent, ' ');
585 lineLen = kIndent;
587 while (inStr[++i] == '\n')
590 else if (isspace(ch))
592 if (lineLen >= 79)
594 outStr += "\n";
595 outStr += std::string(kIndent, ' ');
596 lineLen = kIndent;
599 else if (lineLen > 0)
601 outStr += ch;
602 ++lineLen;
604 ++i;
606 else
608 std::string word;
610 while (i < inStr.length()
611 && lineLen + word.length() < 79
612 && !isspace(inStr[i]) && inStr[i] != '\n')
614 word += inStr[i++];
617 if (lineLen + word.length() >= 79)
619 outStr += "\n";
620 outStr += std::string(kIndent, ' ');
621 lineLen = kIndent;
624 outStr += word;
625 lineLen += word.length();
629 outStr += "\n";
631 return (outStr);
632 } // end munge_description()
634 static void _sdump_messages(dump_params &par)
636 // A little message history:
637 if (Options.dump_message_count > 0)
639 par.text += "Message History\n\n";
640 par.text += get_last_messages(Options.dump_message_count);
644 static void _sdump_screenshot(dump_params &par)
646 par.text += screenshot();
647 par.text += "\n\n";
650 static void _sdump_notes(dump_params &par)
652 std::string &text(par.text);
653 if (note_list.empty())
654 return;
656 text += "\nNotes\nTurn | Place | Note\n";
657 text += "--------------------------------------------------------------\n";
658 for (unsigned i = 0; i < note_list.size(); ++i)
660 text += note_list[i].describe();
661 text += "\n";
663 text += "\n";
666 //---------------------------------------------------------------
668 // dump_location
670 //---------------------------------------------------------------
671 static void _sdump_location(dump_params &par)
673 if (you.absdepth0 == -1
674 && you.where_are_you == BRANCH_MAIN_DUNGEON
675 && you.level_type == LEVEL_DUNGEON)
677 par.text += "You escaped";
679 else if (par.se)
680 par.text += "You were " + prep_branch_level_name();
681 else
682 par.text += "You are " + prep_branch_level_name();
684 par.text += ".";
685 par.text += "\n";
686 } // end dump_location()
688 static void _sdump_religion(dump_params &par)
690 std::string &text(par.text);
691 if (you.religion != GOD_NO_GOD)
693 if (par.se)
694 text += "You worshipped ";
695 else
696 text += "You worship ";
697 text += god_name(you.religion);
698 text += ".\n";
700 if (you.religion != GOD_XOM)
702 if (!player_under_penance())
704 text += god_prayer_reaction();
705 text += "\n";
707 else
709 std::string verb = par.se ? "was" : "is";
711 text += god_name(you.religion);
712 text += " " + verb + " demanding penance.\n";
715 else
717 if (par.se)
718 text += "You were ";
719 else
720 text += "You are ";
721 text += describe_xom_favour(false);
722 text += "\n";
727 static bool _dump_item_origin(const item_def &item, int value)
729 #define fs(x) (flags & (x))
730 const int flags = Options.dump_item_origins;
731 if (flags == IODS_EVERYTHING)
732 return (true);
734 if (fs(IODS_ARTEFACTS)
735 && is_artefact(item) && item_ident(item, ISFLAG_KNOW_PROPERTIES))
737 return (true);
739 if (fs(IODS_EGO_ARMOUR) && item.base_type == OBJ_ARMOUR
740 && item_type_known(item))
742 const int spec_ench = get_armour_ego_type(item);
743 return (spec_ench != SPARM_NORMAL);
746 if (fs(IODS_EGO_WEAPON) && item.base_type == OBJ_WEAPONS
747 && item_type_known(item))
749 return (get_weapon_brand(item) != SPWPN_NORMAL);
752 if (fs(IODS_JEWELLERY) && item.base_type == OBJ_JEWELLERY)
753 return (true);
755 if (fs(IODS_RUNES) && item.base_type == OBJ_MISCELLANY
756 && item.sub_type == MISC_RUNE_OF_ZOT)
758 return (true);
761 if (fs(IODS_RODS) && item.base_type == OBJ_STAVES
762 && item_is_rod(item))
764 return (true);
767 if (fs(IODS_STAVES) && item.base_type == OBJ_STAVES
768 && !item_is_rod(item))
770 return (true);
773 if (fs(IODS_BOOKS) && item.base_type == OBJ_BOOKS)
774 return (true);
776 const int refpr = Options.dump_item_origin_price;
777 if (refpr == -1)
778 return (false);
779 if (value == -1)
780 value = item_value(item, false);
781 return (value >= refpr);
782 #undef fs
785 //---------------------------------------------------------------
787 // dump_inventory
789 //---------------------------------------------------------------
790 static void _sdump_inventory(dump_params &par)
792 int i, j;
794 std::string &text(par.text);
795 std::string text2;
797 int inv_class2[OBJ_GOLD];
798 int inv_count = 0;
799 char tmp_quant[20];
801 for (i = 0; i < OBJ_GOLD; i++)
802 inv_class2[i] = 0;
804 for (i = 0; i < ENDOFPACK; i++)
806 if (you.inv[i].defined())
808 // adds up number of each class in invent.
809 inv_class2[you.inv[i].base_type]++;
810 inv_count++;
814 if (!inv_count)
816 text += "You aren't carrying anything.";
817 text += "\n";
819 else
821 text += "Inventory:\n\n";
823 for (i = 0; i < OBJ_GOLD; i++)
825 if (inv_class2[i] != 0)
827 switch (i)
829 case OBJ_WEAPONS: text += "Hand weapons"; break;
830 case OBJ_MISSILES: text += "Missiles"; break;
831 case OBJ_ARMOUR: text += "Armour"; break;
832 case OBJ_WANDS: text += "Magical devices"; break;
833 case OBJ_FOOD: text += "Comestibles"; break;
834 case OBJ_SCROLLS: text += "Scrolls"; break;
835 case OBJ_JEWELLERY: text += "Jewellery"; break;
836 case OBJ_POTIONS: text += "Potions"; break;
837 case OBJ_BOOKS: text += "Books"; break;
838 case OBJ_STAVES: text += "Magical staves"; break;
839 case OBJ_ORBS: text += "Orbs of Power"; break;
840 case OBJ_MISCELLANY: text += "Miscellaneous"; break;
841 case OBJ_CORPSES: text += "Carrion"; break;
843 default:
844 die("Bad item class");
846 text += "\n";
848 for (j = 0; j < ENDOFPACK; j++)
850 if (you.inv[j].defined() && you.inv[j].base_type == i)
852 text += " ";
853 text += you.inv[j].name(DESC_INVENTORY_EQUIP);
855 inv_count--;
857 int ival = -1;
858 if (par.show_prices)
860 text += " (";
862 itoa(ival = item_value(you.inv[j], true),
863 tmp_quant, 10);
865 text += tmp_quant;
866 text += " gold)";
869 if (origin_describable(you.inv[j])
870 && _dump_item_origin(you.inv[j], ival))
872 text += "\n" " (" + origin_desc(you.inv[j]) + ")";
875 if (is_dumpable_artefact(you.inv[j], false)
876 || Options.dump_book_spells
877 && you.inv[j].base_type == OBJ_BOOKS)
879 text2 = get_item_description(you.inv[j],
880 false,
881 true);
883 text += munge_description(text2);
885 else
887 text += "\n";
894 text += "\n\n";
897 //---------------------------------------------------------------
899 // dump_skills
901 //---------------------------------------------------------------
902 static void _sdump_skills(dump_params &par)
904 std::string &text(par.text);
905 char tmp_quant[20];
907 if (par.se)
908 text += " You had ";
909 else
910 text += " You have ";
912 itoa(you.exp_available, tmp_quant, 10);
913 text += tmp_quant;
914 text += " experience left.";
916 text += "\n";
917 text += "\n";
918 text += " Skills:";
919 text += "\n";
921 dump_skills(text);
922 text += "\n";
923 text += "\n";
926 //---------------------------------------------------------------
928 // Return string of the i-th spell type, with slash if required
930 //---------------------------------------------------------------
931 static std::string spell_type_shortname(int spell_class, bool slash)
933 std::string ret;
935 if (slash)
936 ret = "/";
938 ret += spelltype_short_name(spell_class);
940 return (ret);
941 } // end spell_type_shortname()
943 //---------------------------------------------------------------
945 // dump_spells
947 //---------------------------------------------------------------
948 static void _sdump_spells(dump_params &par)
950 std::string &text(par.text);
951 char tmp_quant[20];
953 // This array helps output the spell types in the traditional order.
954 // this can be tossed as soon as I reorder the enum to the traditional order {dlb}
955 const int spell_type_index[] =
957 SPTYP_HOLY,
958 SPTYP_POISON,
959 SPTYP_FIRE,
960 SPTYP_ICE,
961 SPTYP_EARTH,
962 SPTYP_AIR,
963 SPTYP_CONJURATION,
964 SPTYP_HEXES,
965 SPTYP_CHARMS,
966 SPTYP_DIVINATION,
967 SPTYP_TRANSLOCATION,
968 SPTYP_SUMMONING,
969 SPTYP_TRANSMUTATION,
970 SPTYP_NECROMANCY,
974 int spell_levels = player_spell_levels();
976 std::string verb = par.se? "had" : "have";
978 if (spell_levels == 1)
979 text += "You " + verb + " one spell level left.";
980 else if (spell_levels == 0)
982 verb = par.se? "couldn't" : "cannot";
984 text += "You " + verb + " memorise any spells.";
986 else
988 if (par.se)
989 text += "You had ";
990 else
991 text += "You have ";
992 itoa(spell_levels, tmp_quant, 10);
993 text += tmp_quant;
994 text += " spell levels left.";
997 text += "\n";
999 if (!you.spell_no)
1001 verb = par.se? "didn't" : "don't";
1003 text += "You " + verb + " know any spells.\n\n";
1005 else
1007 verb = par.se? "knew" : "know";
1009 text += "You " + verb + " the following spells:\n\n";
1011 text += " Your Spells Type Power Success Level Hunger" "\n";
1013 for (int j = 0; j < 52; j++)
1015 const char letter = index_to_letter(j);
1016 const spell_type spell = get_spell_by_letter(letter);
1018 if (spell != SPELL_NO_SPELL)
1020 std::string spell_line;
1022 spell_line += letter;
1023 spell_line += " - ";
1024 spell_line += spell_title(spell);
1026 if (spell_line.length() > 24)
1027 spell_line = spell_line.substr(0, 24);
1029 for (int i = spell_line.length(); i < 26; i++)
1030 spell_line += ' ';
1032 bool already = false;
1034 for (int i = 0; spell_type_index[i] != 0; i++)
1036 if (spell_typematch(spell, spell_type_index[i]))
1038 spell_line += spell_type_shortname(spell_type_index[i],
1039 already);
1040 already = true;
1044 for (int i = spell_line.length(); i < 41; ++i)
1045 spell_line += ' ';
1047 spell_line += spell_power_string(spell);
1049 for (int i = spell_line.length(); i < 54; ++i)
1050 spell_line += ' ';
1052 spell_line += failure_rate_to_string(spell_fail(spell));
1054 for (int i = spell_line.length(); i < 66; i++)
1055 spell_line += ' ';
1057 itoa(spell_difficulty(spell), tmp_quant, 10);
1058 spell_line += tmp_quant;
1060 for (int i = spell_line.length(); i < 71; i++)
1061 spell_line += ' ';
1063 spell_line += spell_hunger_string(spell);
1064 spell_line += "\n";
1066 text += spell_line;
1069 text += "\n\n";
1071 } // end dump_spells()
1074 static void _sdump_kills(dump_params &par)
1076 par.text += you.kills->kill_info();
1079 static std::string _sdump_kills_place_info(PlaceInfo place_info,
1080 std::string name = "")
1082 std::string out;
1084 if (name.empty())
1085 name = place_info.short_name();
1087 unsigned int global_total_kills = 0;
1088 for (int i = 0; i < KC_NCATEGORIES; i++)
1089 global_total_kills += you.global_info.mon_kill_num[i];
1091 unsigned int total_kills = 0;
1092 for (int i = 0; i < KC_NCATEGORIES; i++)
1093 total_kills += place_info.mon_kill_num[i];
1095 // Skip places where nothing was killed.
1096 if (total_kills == 0)
1097 return "";
1099 float a, b, c, d, e, f, g;
1101 a = TO_PERCENT(total_kills, global_total_kills);
1102 b = TO_PERCENT(place_info.mon_kill_num[KC_YOU],
1103 you.global_info.mon_kill_num[KC_YOU]);
1104 c = TO_PERCENT(place_info.mon_kill_num[KC_FRIENDLY],
1105 you.global_info.mon_kill_num[KC_FRIENDLY]);
1106 d = TO_PERCENT(place_info.mon_kill_num[KC_OTHER],
1107 you.global_info.mon_kill_num[KC_OTHER]);
1108 e = TO_PERCENT(place_info.mon_kill_exp,
1109 you.global_info.mon_kill_exp);
1110 f = TO_PERCENT(place_info.mon_kill_exp_avail,
1111 you.global_info.mon_kill_exp_avail);
1113 g = std::max<float>(place_info.mon_kill_exp, place_info.mon_kill_exp_avail)
1114 / place_info.levels_seen;
1116 out =
1117 make_stringf("%14s | %5.1f | %5.1f | %5.1f | %5.1f | %5.1f |"
1118 " %5.1f | %13.1f\n",
1119 name.c_str(), a, b, c , d, e, f, g);
1121 out = replace_all(out, " nan ", " N/A ");
1123 return out;
1126 static void _sdump_kills_by_place(dump_params &par)
1128 std::string &text(par.text);
1130 std::vector<PlaceInfo> all_visited =
1131 you.get_all_place_info(true);
1133 std::string result = "";
1135 std::string header =
1136 "Table legend:\n"
1137 " A = Kills in this place as a percentage of kills in entire the game.\n"
1138 " B = Kills by you in this place as a percentage of kills by you in\n"
1139 " the entire game.\n"
1140 " C = Kills by friends in this place as a percentage of kills by\n"
1141 " friends in the entire game.\n"
1142 " D = Other kills in this place as a percentage of other kills in the\n"
1143 " entire game.\n"
1144 " E = Character level experience gained in this place as a percentage of\n"
1145 " character level experience gained in the entire game.\n"
1146 " F = Skills experience gained in this place as a percentage of skills\n"
1147 " experience gained in the entire game.\n"
1148 " G = Experience gained in this place divided by the number of levels of\n"
1149 " this place that you have seen.\n\n";
1151 header += " ";
1152 header += " A B C D E F G\n";
1153 header += " ";
1154 header += "+-------+-------+-------+-------+-------+-------+--------------\n";
1156 std::string footer = " ";
1157 footer += "+-------+-------+-------+-------+-------+-------+--------------\n";
1159 result += _sdump_kills_place_info(you.global_info, "Total");
1161 for (unsigned int i = 0; i < all_visited.size(); i++)
1163 PlaceInfo pi = all_visited[i];
1164 result += _sdump_kills_place_info(pi);
1167 if (result.length() > 0)
1168 text += header + result + footer + "\n";
1171 static void _sdump_overview(dump_params &par)
1173 std::string overview =
1174 formatted_string::parse_string(overview_description_string(false));
1175 trim_string(overview);
1176 par.text += overview;
1177 par.text += "\n\n";
1180 static void _sdump_hiscore(dump_params &par)
1182 if (!par.se)
1183 return;
1185 std::string hiscore = hiscores_format_single_long(*(par.se), true);
1186 trim_string(hiscore);
1187 par.text += hiscore;
1188 par.text += "\n\n";
1191 static void _sdump_monster_list(dump_params &par)
1193 std::string monlist = mpr_monster_list(par.se);
1194 trim_string(monlist);
1195 par.text += monlist;
1196 par.text += "\n\n";
1199 static void _sdump_vault_list(dump_params &par)
1201 if (par.full_id || par.se
1202 #ifdef WIZARD
1203 || you.wizard
1204 #endif
1207 par.text += "Vault maps used:\n\n";
1208 par.text += dump_vault_maps();
1212 static void _sdump_mutations(dump_params &par)
1214 std::string &text(par.text);
1216 if (how_mutated(true, false))
1218 text += "\n";
1219 text += describe_mutations();
1220 text += "\n\n";
1222 } // end dump_mutations()
1224 // ========================================================================
1225 // Public Functions
1226 // ========================================================================
1228 const char *hunger_level(void)
1230 const bool vamp = (you.species == SP_VAMPIRE);
1232 return ((you.hunger <= 1000) ? (vamp ? "bloodless" : "starving") :
1233 (you.hunger <= 1533) ? (vamp ? "near bloodless" : "near starving") :
1234 (you.hunger <= 2066) ? (vamp ? "very thirsty" : "very hungry") :
1235 (you.hunger <= 2600) ? (vamp ? "thirsty" : "hungry") :
1236 (you.hunger < 7000) ? (vamp ? "not thirsty" : "not hungry") :
1237 (you.hunger < 9000) ? "full" :
1238 (you.hunger < 11000) ? "very full"
1239 : (vamp ? "almost alive" : "completely stuffed"));
1242 static std::string morgue_directory()
1244 std::string dir = (!Options.morgue_dir.empty() ? Options.morgue_dir :
1245 !SysEnv.crawl_dir.empty() ? SysEnv.crawl_dir
1246 : "");
1248 if (!dir.empty() && dir[dir.length() - 1] != FILE_SEPARATOR)
1249 dir += FILE_SEPARATOR;
1251 return (dir);
1254 void dump_map(FILE *fp, bool debug, bool dist)
1256 // Duplicate the screenshot() trick.
1257 FixedVector<unsigned, NUM_DCHAR_TYPES> char_table_bk;
1258 char_table_bk = Options.char_table;
1260 init_char_table(CSET_ASCII);
1261 init_show_table();
1263 if (debug)
1265 // Write the whole map out without checking for mappedness. Handy
1266 // for debugging level-generation issues.
1267 for (int y = 0; y < GYM; ++y)
1269 for (int x = 0; x < GXM; ++x)
1271 if (you.pos() == coord_def(x, y))
1272 fputc('@', fp);
1273 else if (testbits(env.pgrid[x][y], FPROP_HIGHLIGHT))
1274 fputc('?', fp);
1275 else if (dist && grd[x][y] == DNGN_FLOOR
1276 && travel_point_distance[x][y] > 0
1277 && travel_point_distance[x][y] < 10)
1279 fputc('0' + travel_point_distance[x][y], fp);
1281 else
1282 fputc(get_feature_def(grd[x][y]).symbol, fp);
1284 fputc('\n', fp);
1287 else
1289 int min_x = GXM-1, max_x = 0, min_y = GYM-1, max_y = 0;
1291 for (int i = X_BOUND_1; i <= X_BOUND_2; i++)
1292 for (int j = Y_BOUND_1; j <= Y_BOUND_2; j++)
1293 if (env.map_knowledge[i][j].known())
1295 if (i > max_x) max_x = i;
1296 if (i < min_x) min_x = i;
1297 if (j > max_y) max_y = j;
1298 if (j < min_y) min_y = j;
1301 for (int y = min_y; y <= max_y; ++y)
1303 for (int x = min_x; x <= max_x; ++x)
1304 fputc(get_cell_glyph(coord_def(x, y)).ch, fp);
1306 fputc('\n', fp);
1310 // Restore char and feature tables
1311 Options.char_table = char_table_bk;
1312 init_show_table();
1315 void dump_map(const char* fname, bool debug, bool dist)
1317 FILE* fp = fopen_replace(fname);
1318 if (!fp)
1319 return;
1321 dump_map(fp, debug, dist);
1323 fclose(fp);
1326 static bool write_dump(const std::string &fname, dump_params &par)
1328 bool succeeded = false;
1330 std::string file_name = morgue_directory();
1332 file_name += strip_filename_unsafe_chars(fname);
1334 StashTrack.update_corpses();
1336 std::string stash_file_name;
1337 stash_file_name = file_name;
1338 stash_file_name += ".lst";
1339 StashTrack.dump(stash_file_name.c_str(), par.full_id);
1341 std::string map_file_name = file_name + ".map";
1342 dump_map(map_file_name.c_str());
1344 file_name += ".txt";
1345 FILE *handle = fopen_replace(file_name.c_str());
1347 #ifdef DEBUG_DIAGNOSTICS
1348 mprf(MSGCH_DIAGNOSTICS, "File name: %s", file_name.c_str());
1349 #endif
1351 if (handle != NULL)
1353 fputs(par.text.c_str(), handle);
1354 fclose(handle);
1355 succeeded = true;
1356 mprf("Char dumped to '%s'.", file_name.c_str());
1358 else
1359 mprf(MSGCH_ERROR, "Error opening file '%s'", file_name.c_str());
1361 return (succeeded);
1364 void display_notes()
1366 formatted_scroller scr;
1367 scr.set_flags(MF_START_AT_END);
1368 scr.set_tag("notes");
1369 scr.set_highlighter(new MenuHighlighter);
1370 scr.set_title(new MenuEntry("Turn | Place | Note"));
1371 for (unsigned int i = 0; i < note_list.size(); ++i)
1373 std::string prefix = note_list[i].describe(true, true, false);
1374 std::string suffix = note_list[i].describe(false, false, true);
1375 if (suffix.empty())
1376 continue;
1378 int spaceleft = get_number_of_cols() - prefix.length() - 1;
1379 if (spaceleft <= 0)
1380 return;
1382 // Use smarter linebreak function.
1383 // was: linebreak_string(suffix, spaceleft - 4, spaceleft);
1384 linebreak_string2(suffix, spaceleft);
1385 std::vector<std::string> parts = split_string("\n", suffix);
1386 if (parts.empty()) // Disregard pure-whitespace notes.
1387 continue;
1389 scr.add_entry(new MenuEntry(prefix + parts[0]));
1390 for (unsigned int j = 1; j < parts.size(); ++j)
1392 scr.add_entry(new MenuEntry(std::string(prefix.length()-2, ' ') +
1393 std::string("| ") + parts[j]));
1396 scr.show();
1397 redraw_screen();
1400 #ifdef DGL_WHEREIS
1401 ///////////////////////////////////////////////////////////////////////////
1402 // whereis player
1403 void whereis_record(const char *status)
1405 const std::string file_name =
1406 morgue_directory()
1407 + strip_filename_unsafe_chars(you.your_name)
1408 + std::string(".where");
1410 if (FILE *handle = fopen_replace(file_name.c_str()))
1412 fprintf(handle, "%s:status=%s\n",
1413 xlog_status_line().c_str(),
1414 status? status : "");
1415 fclose(handle);
1418 #endif
1421 ///////////////////////////////////////////////////////////////////////////////
1422 // Turn timestamps
1424 // For DGL installs, write a timestamp at regular intervals into a file in
1425 // the morgue directory. The timestamp file is named
1426 // "timestamp-<player>-<starttime>.ts". All timestamps are standard Unix
1427 // time_t, but currently only the low 4 bytes are saved even on systems
1428 // with 64-bit time_t.
1430 // Timestamp files are append only, and Crawl will check and handle cases
1431 // where a previous Crawl process crashed at a higher turn count on the same
1432 // game.
1434 // Having timestamps associated with the game allows for much easier seeking
1435 // within Crawl ttyrecs by external tools such as FooTV.
1437 #ifdef DGL_TURN_TIMESTAMPS
1439 #include <sys/stat.h>
1441 // File-format version for timestamp files. Crawl will never append to a
1442 const uint32_t DGL_TIMESTAMP_VERSION = 1;
1443 const int VERSION_SIZE = sizeof(DGL_TIMESTAMP_VERSION);
1444 const int TIMESTAMP_SIZE = sizeof(uint32_t);
1446 // Returns the size of the opened file with the give FILE* handle.
1447 unsigned long _file_size(FILE *handle)
1449 struct stat fs;
1450 const int err = fstat(fileno(handle), &fs);
1451 return err? 0 : fs.st_size;
1454 // Returns the name of the timestamp file based on the morgue_dir,
1455 // character name and the game start time.
1456 std::string dgl_timestamp_filename()
1458 const std::string filename =
1459 ("timestamp-" + you.your_name + "-" + make_file_time(you.birth_time));
1460 return morgue_directory() + strip_filename_unsafe_chars(filename) + ".ts";
1463 // Returns true if the given file exists and is not a timestamp file
1464 // of a known version.
1465 bool dgl_unknown_timestamp_file(const std::string &filename)
1467 if (FILE *inh = fopen(filename.c_str(), "rb"))
1469 reader r(inh);
1470 const uint32_t file_version = unmarshallInt(r);
1471 fclose(inh);
1472 return (file_version != DGL_TIMESTAMP_VERSION);
1474 return (false);
1477 // Returns a filehandle to use to write turn timestamps, NULL if
1478 // timestamps should not be written.
1479 FILE *dgl_timestamp_filehandle()
1481 static FILE *timestamp_file;
1482 static bool opened_file = false;
1483 if (!opened_file)
1485 opened_file = true;
1487 const std::string filename = dgl_timestamp_filename();
1488 // First check if there's already a timestamp file. If it exists
1489 // but has a different version, we cannot safely modify it, so bail.
1490 if (!dgl_unknown_timestamp_file(filename))
1491 timestamp_file = fopen(filename.c_str(), "ab");
1493 return timestamp_file;
1496 // Records a timestamp in the .ts file at the given offset. If no timestamp
1497 // file exists, a new file will be created.
1498 void dgl_record_timestamp(unsigned long file_offset, time_t time)
1500 static bool timestamp_first_write = true;
1501 if (FILE *ftimestamp = dgl_timestamp_filehandle())
1503 writer w(dgl_timestamp_filename(), ftimestamp, true);
1504 if (timestamp_first_write)
1506 unsigned long ts_size = _file_size(ftimestamp);
1507 if (!ts_size)
1509 marshallInt(w, DGL_TIMESTAMP_VERSION);
1510 ts_size += sizeof(DGL_TIMESTAMP_VERSION);
1513 // It's possible that the file we want to write is already
1514 // larger than the offset we expect if the game previously
1515 // crashed. When the game crashes, turn count is
1516 // effectively rewound to the point of the last save. In
1517 // such cases, we should not add timestamps until we reach
1518 // the correct turn count again.
1519 if (ts_size && ts_size > file_offset)
1520 return;
1522 if (file_offset > ts_size)
1524 const int backlog =
1525 (file_offset - ts_size) / TIMESTAMP_SIZE;
1526 for (int i = 0; i < backlog; ++i)
1527 marshallInt(w, 0);
1530 timestamp_first_write = false;
1532 fseek(ftimestamp, 0, SEEK_END);
1533 // [ds] FIXME: Eventually switch to 8 byte timestamps.
1534 marshallInt(w, static_cast<uint32_t>(time));
1535 fflush(ftimestamp);
1539 // Record timestamps every so many turns:
1540 const int TIMESTAMP_TURN_INTERVAL = 100;
1541 // Stop recording timestamps after this turncount.
1542 const long TIMESTAMP_TURN_MAX = 500000L;
1543 void dgl_record_timestamp(long turn)
1545 if (turn && turn < TIMESTAMP_TURN_MAX && !(turn % TIMESTAMP_TURN_INTERVAL))
1547 const time_t now = time(NULL);
1548 const unsigned long offset =
1549 (VERSION_SIZE +
1550 (turn / TIMESTAMP_TURN_INTERVAL - 1) * TIMESTAMP_SIZE);
1551 dgl_record_timestamp(offset, now);
1555 #endif
1557 // Records a timestamp for the current player turn if appropriate.
1558 void record_turn_timestamp()
1560 #ifdef DGL_TURN_TIMESTAMPS
1561 if (crawl_state.need_save)
1562 dgl_record_timestamp(you.num_turns);
1563 #endif