1 /**
2  * @file
3  * @brief Miscellaneous debugging functions.
4 **/
5 
6 #include "AppHdr.h"
7 
8 #include "dbg-util.h"
9 
10 #include "artefact.h"
11 #include "directn.h"
12 #include "dungeon.h"
13 #include "format.h"
14 #include "item-name.h"
15 #include "libutil.h"
16 #include "macro.h"
17 #include "message.h"
18 #include "options.h"
19 #include "religion.h"
20 #include "scroller.h"
21 #include "shopping.h"
22 #include "skills.h"
23 #include "spl-util.h"
24 #include "state.h"
25 #include "stringutil.h"
26 
debug_prompt_for_monster()27 monster_type debug_prompt_for_monster()
28 {
29     char specs[1024];
30 
31     mprf(MSGCH_PROMPT, "Which monster by name? ");
32     if (!cancellable_get_line_autohist(specs, sizeof specs))
33     {
34         if (specs[0] == '\0')
35             return MONS_NO_MONSTER;
36 
37         return get_monster_by_name(specs, true);
38     }
39     return MONS_NO_MONSTER;
40 }
41 
level_vault_names(bool force_all)42 vector<string> level_vault_names(bool force_all)
43 {
44     vector<string> result;
45     for (auto &vault : env.level_vaults)
46     {
47         // TODO: should this suppress layouts? The code it is replacing did.
48         if (!force_all && (vault->map.has_tag_suffix("dummy")
49                             || vault->map.has_tag("no_dump")))
50         {
51             continue;
52         }
53         string vault_name = vault->map.name;
54         if (vault->map.subvault_places.size())
55         {
56             vault_name += " [";
57             for (unsigned int j = 0; j < vault->map.subvault_places.size(); ++j)
58             {
59                 vault_name += vault->map.subvault_places[j].subvault->name;
60                 if (j < vault->map.subvault_places.size() - 1)
61                     vault_name += ", ";
62             }
63             vault_name += "]";
64         }
65         result.emplace_back(vault_name);
66     }
67     return result;
68 }
69 
debug_dump_levgen()70 void debug_dump_levgen()
71 {
72     if (crawl_state.game_is_arena())
73         return;
74 
75     CrawlHashTable &props = env.properties;
76 
77     string method;
78     string type;
79 
80     if (crawl_state.generating_level)
81     {
82         mpr("Currently generating level.");
83         method = env.level_build_method;
84         type   = comma_separated_line(env.level_layout_types.begin(),
85                                       env.level_layout_types.end(), ", ");
86 
87         if (!env.placing_vault.empty())
88             mprf("Vault being placed: %s", env.placing_vault.c_str());
89         if (!env.new_subvault_names.empty())
90         {
91             mprf("Subvaults: %s", comma_separated_line(
92                  env.new_subvault_names.begin(), env.new_subvault_names.end(),
93                  ", ").c_str());
94         }
95     }
96     else
97     {
98         if (!props.exists(BUILD_METHOD_KEY))
99             method = "ABSENT";
100         else
101             method = props[BUILD_METHOD_KEY].get_string();
102 
103         if (!props.exists(LAYOUT_TYPE_KEY))
104             type = "ABSENT";
105         else
106             type = props[LAYOUT_TYPE_KEY].get_string();
107     }
108 
109     mprf("Level build method = %s, level layout type  = %s, absdepth0 = %d",
110          method.c_str(), type.c_str(), env.absdepth0);
111 
112     if (!env.level_vaults.empty())
113     {
114         mpr("Level vaults:");
115         auto vaults = level_vault_names(true);
116         for (auto &vname : vaults)
117             mprf("    %s", vname.c_str());
118     }
119     mpr("");
120 }
121 
debug_show_builder_logs()122 void debug_show_builder_logs()
123 {
124     if (!you.props.exists("debug_builder_logs"))
125     {
126         mprf("This save was not generated on a build that stores logs.");
127         return;
128     }
129     const string cur_level = level_id::current().describe();
130     CrawlHashTable &log_table = you.props["debug_builder_logs"].get_table();
131     if (!log_table.exists(cur_level)
132         || log_table[cur_level].get_string().size() == 0)
133     {
134         mprf("No builder logs are saved for %s.", cur_level.c_str());
135         return;
136     }
137     string props_text = log_table[cur_level].get_string();
138     auto log = formatted_string::parse_string(trim_string_right(props_text));
139     formatted_scroller log_scroller;
140     log_scroller.set_more();
141     log_scroller.add_formatted_string(log, false);
142     log_scroller.show();
143 }
144 
debug_coord_str(const coord_def & pos)145 string debug_coord_str(const coord_def &pos)
146 {
147     return make_stringf("(%d, %d)%s", pos.x, pos.y,
148                         !in_bounds(pos) ? " <OoB>" : "");
149 }
150 
debug_mon_str(const monster * mon)151 string debug_mon_str(const monster* mon)
152 {
153     const int midx = mon->mindex();
154     if (invalid_monster_index(midx))
155         return make_stringf("Invalid monster index %d", midx);
156 
157     string out = "Monster '" + mon->full_name(DESC_PLAIN) + "' ";
158     out += make_stringf("%s [midx = %d]", debug_coord_str(mon->pos()).c_str(),
159                         midx);
160 
161     return out;
162 }
163 
_debug_mid_name(mid_t mid)164 static string _debug_mid_name(mid_t mid)
165 {
166     if (mid == MID_PLAYER)
167         return make_stringf("player %s", debug_coord_str(you.pos()).c_str());
168     else
169     {
170         monster * const mons = monster_by_mid(mid);
171         if (mons)
172             return debug_mon_str(mons);
173         else
174             return make_stringf("bad monster[%" PRImidt"]", mid);
175     }
176 }
177 
debug_constriction_string(const actor * act)178 string debug_constriction_string(const actor *act)
179 {
180     string s;
181     if (act->constricting)
182     {
183         for (const auto &entry : *act->constricting)
184         {
185             s += make_stringf("Constricting %s for %d ticks.\n",
186                         _debug_mid_name(entry.first).c_str(), entry.second);
187         }
188     }
189 
190     if (act->constricted_by)
191     {
192         s += make_stringf("Constricted by %s for %d ticks.\n",
193                 _debug_mid_name(act->constricted_by).c_str(),
194                     actor_by_mid(act->constricted_by)->constricting->find(act->mid)->second);
195     }
196     return s;
197 }
198 
debug_dump_constriction(const actor * act)199 void debug_dump_constriction(const actor *act)
200 {
201     string desc = debug_constriction_string(act);
202     if (!desc.empty())
203         fprintf(stderr, "%s", desc.c_str());
204 }
205 
debug_dump_mon(const monster * mon,bool recurse)206 void debug_dump_mon(const monster* mon, bool recurse)
207 {
208     const int midx = mon->mindex();
209     if (invalid_monster_index(midx) || invalid_monster_type(mon->type))
210         return;
211 
212     fprintf(stderr, "<<<<<<<<<\n");
213 
214     fprintf(stderr, "Name: %s\n", mon->name(DESC_PLAIN, true).c_str());
215     fprintf(stderr, "Base name: %s\n",
216             mon->base_name(DESC_PLAIN, true).c_str());
217     fprintf(stderr, "Full name: %s\n\n",
218             mon->full_name(DESC_PLAIN).c_str());
219 
220     if (in_bounds(mon->pos()))
221     {
222         string feat = raw_feature_description(mon->pos());
223         fprintf(stderr, "On/in/over feature: %s\n\n", feat.c_str());
224     }
225 
226     fprintf(stderr, "Foe: ");
227     if (mon->foe == MHITNOT)
228         fprintf(stderr, "none");
229     else if (mon->foe == MHITYOU)
230         fprintf(stderr, "player");
231     else if (invalid_monster_index(mon->foe))
232         fprintf(stderr, "invalid monster index %d", mon->foe);
233     else if (mon->foe == midx)
234         fprintf(stderr, "self");
235     else
236         fprintf(stderr, "%s", debug_mon_str(&env.mons[mon->foe]).c_str());
237 
238     fprintf(stderr, "\n");
239 
240     fprintf(stderr, "Target: ");
241     if (mon->target.origin())
242         fprintf(stderr, "none\n");
243     else
244         fprintf(stderr, "%s\n", debug_coord_str(mon->target).c_str());
245 
246     int target = MHITNOT;
247     fprintf(stderr, "At target: ");
248     if (mon->target.origin())
249         fprintf(stderr, "N/A");
250     else if (mon->target == you.pos())
251     {
252         fprintf(stderr, "player");
253         target = MHITYOU;
254     }
255     else if (mon->target == mon->pos())
256     {
257         fprintf(stderr, "self");
258         target = midx;
259     }
260     else if (in_bounds(mon->target))
261     {
262         target = env.mgrid(mon->target);
263 
264         if (target == NON_MONSTER)
265             fprintf(stderr, "nothing");
266         else if (target == midx)
267             fprintf(stderr, "improperly linked self");
268         else if (target == mon->foe)
269             fprintf(stderr, "same as foe");
270         else if (invalid_monster_index(target))
271             fprintf(stderr, "invalid monster index %d", target);
272         else
273             fprintf(stderr, "%s", debug_mon_str(&env.mons[target]).c_str());
274     }
275     else
276         fprintf(stderr, "<OoB>");
277 
278     fprintf(stderr, "\n");
279     debug_dump_constriction(mon);
280 
281     if (mon->is_patrolling())
282     {
283         fprintf(stderr, "Patrolling: %s\n\n",
284                 debug_coord_str(mon->patrol_point).c_str());
285     }
286 
287     if (mon->travel_target != MTRAV_NONE)
288     {
289         fprintf(stderr, "\nTravelling:\n");
290         fprintf(stderr, "    travel_target      = %d\n", mon->travel_target);
291         fprintf(stderr, "    travel_path.size() = %u\n",
292                 (unsigned int)mon->travel_path.size());
293 
294         if (!mon->travel_path.empty())
295         {
296             fprintf(stderr, "    next travel step: %s\n",
297                     debug_coord_str(mon->travel_path.back()).c_str());
298             fprintf(stderr, "    last travel step: %s\n",
299                     debug_coord_str(mon->travel_path.front()).c_str());
300         }
301     }
302     fprintf(stderr, "\n");
303 
304     fprintf(stderr, "Inventory:\n");
305     for (int i = 0; i < NUM_MONSTER_SLOTS; ++i)
306     {
307         const int idx = mon->inv[i];
308 
309         if (idx == NON_ITEM)
310             continue;
311 
312         fprintf(stderr, "    slot #%d: ", i);
313 
314         if (idx < 0 || idx > MAX_ITEMS)
315         {
316             fprintf(stderr, "invalid item index %d\n", idx);
317             continue;
318         }
319         const item_def &item(env.item[idx]);
320 
321         if (!item.defined())
322         {
323             fprintf(stderr, "invalid item\n");
324             continue;
325         }
326 
327         fprintf(stderr, "%s", item.name(DESC_PLAIN, false, true).c_str());
328 
329         if (!item.held_by_monster())
330         {
331             fprintf(stderr, " [not held by monster, pos = %s]",
332                     debug_coord_str(item.pos).c_str());
333         }
334         else if (item.holding_monster() != mon)
335         {
336             fprintf(stderr, " [held by other monster: %s]",
337                     debug_mon_str(item.holding_monster()).c_str());
338         }
339 
340         fprintf(stderr, "\n");
341     }
342     fprintf(stderr, "\n");
343 
344 
345     bool found_spells = false;
346     for (unsigned i = 0; i < mon->spells.size(); ++i)
347     {
348         spell_type spell = mon->spells[i].spell;
349 
350         if (!found_spells)
351         {
352             fprintf(stderr, "Spells:\n");
353             found_spells = true;
354         }
355 
356         fprintf(stderr, "    slot #%u: ", i);
357         if (!is_valid_spell(spell))
358             fprintf(stderr, "Invalid spell #%d\n", (int) spell);
359         else
360             fprintf(stderr, "%s\n", spell_title(spell));
361         fprintf(stderr, "\n");
362     }
363 
364     fprintf(stderr, "attitude: %d, behaviour: %d, number: %d, flags: 0x%" PRIx64"\n",
365             mon->attitude, mon->behaviour, mon->number, mon->flags.flags);
366 
367     fprintf(stderr, "colour: %d, foe_memory: %d, shield_blocks:%d, "
368                   "experience: %u\n",
369             mon->colour, mon->foe_memory, mon->shield_blocks, mon->experience);
370 
371     fprintf(stderr, "god: %s, seen_context: %d\n",
372             god_name(mon->god).c_str(), mon->seen_context);
373 
374     fprintf(stderr, ">>>>>>>>>\n\n");
375 
376     if (!recurse)
377         return;
378 
379     if (!invalid_monster_index(mon->foe) && mon->foe != midx
380         && !invalid_monster_type(env.mons[mon->foe].type))
381     {
382         fprintf(stderr, "Foe:\n");
383         debug_dump_mon(&env.mons[mon->foe], false);
384     }
385 
386     if (!invalid_monster_index(target) && target != midx
387         && target != mon->foe
388         && !invalid_monster_type(env.mons[target].type))
389     {
390         fprintf(stderr, "Target:\n");
391         debug_dump_mon(&env.mons[target], false);
392     }
393 }
394 
debug_dump_item(const char * name,int num,const item_def & item,const char * format,...)395 void debug_dump_item(const char *name, int num, const item_def &item,
396                        const char *format, ...)
397 {
398 #ifdef DEBUG_FATAL
399     const msg_channel_type chan = MSGCH_WARN;
400 #else
401     const msg_channel_type chan = MSGCH_ERROR;
402 #endif
403 
404     va_list args;
405     va_start(args, format);
406     string msg = vmake_stringf(format, args);
407     va_end(args);
408 
409     mprf(chan, "%s", msg.c_str());
410     mprf(chan, "%s", name);
411 
412     mprf("    item #%d:  base: %d; sub: %d; plus: %d; plus2: %d; special: %d",
413          num, item.base_type, item.sub_type,
414          item.plus, item.plus2, item.special);
415 
416     mprf("    quant: %d; ident: 0x%08" PRIx32"; ident_type: %d",
417          item.quantity, item.flags, get_ident_type(item));
418 
419     mprf("    x: %d; y: %d; link: %d", item.pos.x, item.pos.y, item.link);
420 
421 #ifdef DEBUG_FATAL
422     if (!crawl_state.game_crashed)
423         die("%s %s", msg.c_str(), name);
424 #endif
425     crawl_state.cancel_cmd_repeat();
426 }
427 
debug_prompt_for_skill(const char * prompt)428 skill_type debug_prompt_for_skill(const char *prompt)
429 {
430     char specs[80];
431     msgwin_get_line_autohist(prompt, specs, sizeof(specs));
432     if (specs[0] == '\0')
433         return SK_NONE;
434 
435     return skill_from_name(lowercase_string(specs).c_str());
436 }
437 
438 /**
439  * Get a skill type from a skill name, accepting abbreviations.
440  *
441  * @see str_to_skill for an exact version.
442  */
skill_from_name(const char * name)443 skill_type skill_from_name(const char *name)
444 {
445     skill_type skill = SK_NONE;
446 
447     for (skill_type sk = SK_FIRST_SKILL; sk < NUM_SKILLS; ++sk)
448     {
449         string sk_name = lowercase_string(skill_name(sk));
450 
451         size_t pos = sk_name.find(name);
452         if (pos != string::npos)
453         {
454             skill = sk;
455             // We prefer prefixes over partial matches.
456             if (!pos)
457                 break;
458         }
459     }
460 
461     return skill;
462 }
463 
debug_art_val_str(const item_def & item)464 string debug_art_val_str(const item_def& item)
465 {
466     ASSERT(is_artefact(item));
467 
468     return make_stringf("art val: %d, item val: %d",
469                         artefact_value(item), item_value(item, true));
470 }
471 
debug_cap_stat(int stat)472 int debug_cap_stat(int stat)
473 {
474     return stat < -128 ? -128 :
475            stat >  127 ?  127
476                        : stat;
477 }
478 
debug_list_vacant_keys()479 void debug_list_vacant_keys()
480 {
481     // Excluding / and * as they are prefix keys
482     const char *base_keys = "`~[{]}\\|-_=+;:'\",<.>?!@#$%^&()1234567890";
483     string message = "Available keys:";
484     function<void(char, string)> check = [&message](char k, const string name)
485             {
486                 command_type cmd = key_to_command(k, KMC_DEFAULT);
487                 if (cmd == CMD_NO_CMD)
488                 {
489                     message += ' ';
490                     message += name;
491                 }
492             };
493     for (const char *k = base_keys; *k; k++)
494         check(*k, string(1, *k));
495 
496     for (char base = 'A'; base <= 'Z'; base++)
497     {
498         check(base, string(1, base));
499 
500         char lower = tolower_safe(base);
501         check(lower, string(1, lower));
502 
503         char ctrl = CONTROL(base);
504         check(ctrl, string{'^', base});
505     }
506 
507     mpr(message);
508 }
509 
510 #ifdef DEBUG
511 static FILE *debugf = 0;
512 
debuglog(const char * format,...)513 void debuglog(const char *format, ...)
514 {
515     va_list args;
516 
517     if (!debugf)
518     {
519         debugf = fopen("debuglog.txt", "w");
520         ASSERT(debugf);
521     }
522 
523     va_start(args, format);
524     vfprintf(debugf, format, args);
525     va_end(args);
526     fflush(debugf);
527 }
528 #endif
529 
530 #ifndef DEBUG_DIAGNOSTICS
wizard_toggle_dprf()531 void wizard_toggle_dprf()
532 {
533     mpr("Diagnostic messages are available only in debug builds.");
534 }
535 #else
536 // Be sure to change enum diag_type in mpr.h to match.
537 static const char* diag_names[] =
538 {
539     "normal",
540     "level builder",
541     "skill",
542     "combat",
543     "beam",
544     "noise",
545     "abyss",
546     "monplace",
547 #ifdef DEBUG_MONSPEAK
548     "speech",
549 #endif
550 #ifdef DEBUG_MONINDEX
551     "monster index",
552 #endif
553 };
554 
wizard_toggle_dprf()555 void wizard_toggle_dprf()
556 {
557     COMPILE_CHECK(ARRAYSZ(diag_names) == NUM_DIAGNOSTICS);
558 
559     while (true)
560     {
561         string line;
562         for (int i = 0; i < NUM_DIAGNOSTICS; i++)
563         {
564             line += make_stringf("%s[%c] %-10s%s ",
565                                  Options.quiet_debug_messages[i] ? "<white>" : "",
566                                  i + '0',
567                                  diag_names[i],
568                                  Options.quiet_debug_messages[i] ? "</white>" : "");
569             if (i % 5 == 4 || i == NUM_DIAGNOSTICS - 1)
570             {
571                 mprf(MSGCH_PROMPT, "%s", line.c_str());
572                 line.clear();
573             }
574         }
575         mprf(MSGCH_PROMPT, "Toggle which debug class (ESC to exit)? ");
576 
577         int keyin = toalower(get_ch());
578 
579         if (key_is_escape(keyin) || keyin == ' '
580             || keyin == '\r' || keyin == '\n')
581         {
582             canned_msg(MSG_OK);
583             return;
584         }
585 
586         if (keyin < '0' || keyin >= '0' + NUM_DIAGNOSTICS)
587             continue;
588 
589         int diag = keyin - '0';
590         Options.quiet_debug_messages.set(diag, !Options.quiet_debug_messages[diag]);
591         mprf("%s messages will be %s.", diag_names[diag],
592              Options.quiet_debug_messages[diag] ? "suppressed" : "printed");
593         return;
594     }
595 }
596 #endif
597