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