1 /**
2 * @file
3 * @brief Objstat: monster and item generation statistics
4 **/
5
6 #include "AppHdr.h"
7
8 #include "dbg-objstat.h"
9
10 #include <cerrno>
11 #include <cmath>
12 #include <sstream>
13
14 #include "artefact.h"
15 #include "branch.h"
16 #include "corpse.h"
17 #include "dbg-maps.h"
18 #include "dbg-util.h"
19 #include "dungeon.h"
20 #include "end.h"
21 #include "env.h"
22 #include "feature.h"
23 #include "initfile.h"
24 #include "invent.h"
25 #include "item-name.h"
26 #include "item-prop.h"
27 #include "item-prop-enum.h"
28 #include "item-status-flag-type.h"
29 #include "items.h"
30 #include "libutil.h"
31 #include "maps.h"
32 #include "message.h"
33 #include "mon-util.h"
34 #include "ng-init.h"
35 #include "shopping.h"
36 #include "spl-book.h"
37 #include "state.h"
38 #include "stepdown.h"
39 #include "stringutil.h"
40 #include "tag-version.h"
41 #include "version.h"
42
43 #ifdef DEBUG_STATISTICS
44
45 #define STAT_PRECISION 2
46
47 const static char *stat_out_prefix = "objstat_";
48 const static char *stat_out_ext = ".txt";
49 static FILE *stat_outf;
50
51 enum item_base_type
52 {
53 ITEM_GOLD,
54 ITEM_SCROLLS,
55 ITEM_POTIONS,
56 ITEM_WANDS,
57 ITEM_WEAPONS,
58 ITEM_MISSILES,
59 ITEM_STAVES,
60 ITEM_ARMOUR,
61 ITEM_JEWELLERY,
62 ITEM_MISCELLANY,
63 ITEM_SPELLBOOKS,
64 ITEM_MANUALS,
65 NUM_ITEM_BASE_TYPES,
66 ITEM_IGNORE = 100,
67 };
68
69 // Holds item data needed for item statistics and maps values from item_def
70 // where changes are needed.
71 class objstat_item
72 {
73 public:
objstat_item(item_base_type ct,int st)74 objstat_item(item_base_type ct, int st) : base_type(ct), sub_type(st)
75 {
76 }
77
78 objstat_item(const item_def &item);
79
80 item_base_type base_type;
81 int sub_type;
82 int quantity;
83 int plus;
84 int brand;
85 vector<spell_type> spells;
86
87 bool in_vault;
88 bool is_arte;
89 bool in_shop;
90 bool held_mons;
91 };
92
93 static level_id all_lev(NUM_BRANCHES, -1);
94 static set<level_id> stat_levels;
95 static int num_branches = 0;
96 static int num_levels = 0;
97
98 // Ex: item_recs[level][item.base_type][item.sub_type][field]
99 static map<level_id, map<item_base_type, map<int, map<string, int> > > >
100 item_recs;
101 static map<item_base_type, vector<string> > item_fields = {
102 { ITEM_GOLD,
103 { "Num", "NumVault", "NumMons", "NumMin", "NumMax", "NumSD" }
104 },
105 { ITEM_SCROLLS,
106 { "Num", "NumVault", "NumShop", "NumMons", "NumMin", "NumMax", "NumSD" }
107 },
108 { ITEM_POTIONS,
109 { "Num", "NumVault", "NumShop", "NumMons", "NumMin", "NumMax", "NumSD" }
110 },
111 { ITEM_WANDS,
112 { "Num", "NumVault", "NumShop", "NumMons", "NumMin", "NumMax", "NumSD",
113 "Chrg", "ChrgVault", "ChrgShop", "ChrgMons" },
114 },
115 { ITEM_WEAPONS,
116 { "Num", "NumBrand", "NumArte", "NumVault", "NumShop", "NumMons",
117 "NumMin", "NumMax", "NumSD", "Ench", "EnchBrand", "EnchArte",
118 "EnchVault", "EnchShop", "EnchMons" },
119 },
120 { ITEM_MISSILES,
121 { "Num", "NumBrand", "NumVault", "NumShop", "NumMons", "Num",
122 "NumMin", "NumMax", "NumSD" },
123 },
124 { ITEM_STAVES,
125 { "Num", "NumVault", "NumShop", "NumMons", "Num", "NumMin", "NumMax",
126 "NumSD" },
127 },
128 { ITEM_ARMOUR,
129 { "Num", "NumBrand", "NumArte", "NumVault", "NumShop", "NumMons",
130 "NumMin", "NumMax", "NumSD", "Ench", "EnchBrand", "EnchArte",
131 "EnchVault", "EnchShop", "EnchMons" }
132 },
133 { ITEM_JEWELLERY,
134 { "Num", "NumArte", "NumVault", "NumShop", "NumMons", "NumMin",
135 "NumMax", "NumSD" },
136 },
137 { ITEM_MISCELLANY,
138 { "Num", "NumVault", "NumShop", "NumMin", "NumMax", "NumSD" },
139 },
140 { ITEM_SPELLBOOKS,
141 { "Num", "NumVault", "NumShop", "NumMin", "NumMax", "NumSD" },
142 },
143 { ITEM_MANUALS,
144 { "Num", "NumVault", "NumShop", "NumMin", "NumMax", "NumSD" },
145 },
146 };
147
148 enum stat_category_type
149 {
150 CATEGORY_ALL, // regardless of below categories
151 CATEGORY_ARTEFACT, // is artefact
152 CATEGORY_VAULT, // in a vault
153 CATEGORY_SHOP, // in a shop
154 CATEGORY_MONSTER, // held by a monster
155 NUM_STAT_CATEGORIES
156 };
157
158 // Ex: brand_recs[level][item.base_type][item.sub_type][CATEGORY_ALL][brand];
159 static map<level_id, map<item_base_type, map<int, map<stat_category_type,
160 map<int, int> > > > > brand_recs;
161 static map<item_base_type, vector<string> > brand_fields = {
162 {ITEM_WEAPONS,
163 { "Brands", "BrandsArte", "BrandsVault", "BrandsShop", "BrandsMons" } },
164 {ITEM_ARMOUR,
165 {"Brands", "BrandsArte", "BrandsVault", "BrandsShop", "BrandsMons" } },
166 {ITEM_MISSILES, { "Brands", "BrandsVault", "BrandsShop", "BrandsMons" } },
167 };
168
169 static set<monster_type> objstat_monsters;
170 // Ex: monster_recs[level][mc]["Num"]
171 static map<level_id, map<monster_type, map <string, int> > > monster_recs;
172 static const vector<string> monster_fields = {
173 "Num", "NumVault", "NumMin", "NumMax", "NumSD", "MonsHD", "MonsHP",
174 "MonsXP", "TotalXP", "TotalXPVault"
175 };
176
177 static set<dungeon_feature_type> objstat_features;
178 typedef map<dungeon_feature_type, map <string, int> > feature_stats;
179 static map<level_id, feature_stats> feature_recs;
180 static const vector<string> feature_fields = {
181 "Num", "NumVault", "NumMin", "NumMax", "NumSD",
182 };
183
184 static set<spell_type> objstat_spells;
185 // Ex: spell_recs[level][spell]["Num"]
186 static map<level_id, map<spell_type, map<string, int> > > spell_recs;
187 static const vector<string> spell_fields = {
188 "Num", "NumVault", "NumShop", "NumArte", "NumMin", "NumMax", "NumSD",
189 "Chance", "ChanceVault", "ChanceShop", "ChanceArte"
190 };
191
_item_base_type(const item_def & item)192 static item_base_type _item_base_type(const item_def &item)
193 {
194 item_base_type type;
195 switch (item.base_type)
196 {
197 case OBJ_MISCELLANY:
198 type = ITEM_MISCELLANY;
199 break;
200 case OBJ_BOOKS:
201 if (item.sub_type == BOOK_MANUAL)
202 type = ITEM_MANUALS;
203 else
204 type = ITEM_SPELLBOOKS;
205 break;
206 case OBJ_GOLD:
207 type = ITEM_GOLD;
208 break;
209 case OBJ_SCROLLS:
210 type = ITEM_SCROLLS;
211 break;
212 case OBJ_POTIONS:
213 type = ITEM_POTIONS;
214 break;
215 case OBJ_WANDS:
216 type = ITEM_WANDS;
217 break;
218 case OBJ_WEAPONS:
219 type = ITEM_WEAPONS;
220 break;
221 case OBJ_MISSILES:
222 type = ITEM_MISSILES;
223 break;
224 case OBJ_STAVES:
225 type = ITEM_STAVES;
226 break;
227 case OBJ_ARMOUR:
228 type = ITEM_ARMOUR;
229 break;
230 case OBJ_JEWELLERY:
231 type = ITEM_JEWELLERY;
232 break;
233 default:
234 type = ITEM_IGNORE;
235 break;
236 }
237 return type;
238 }
239
_item_orig_base_type(item_base_type base_type)240 static object_class_type _item_orig_base_type(item_base_type base_type)
241 {
242 object_class_type type;
243 switch (base_type)
244 {
245 case ITEM_GOLD:
246 type = OBJ_GOLD;
247 break;
248 case ITEM_SCROLLS:
249 type = OBJ_SCROLLS;
250 break;
251 case ITEM_POTIONS:
252 type = OBJ_POTIONS;
253 break;
254 case ITEM_WANDS:
255 type = OBJ_WANDS;
256 break;
257 case ITEM_WEAPONS:
258 type = OBJ_WEAPONS;
259 break;
260 case ITEM_MISSILES:
261 type = OBJ_MISSILES;
262 break;
263 case ITEM_STAVES:
264 type = OBJ_STAVES;
265 break;
266 case ITEM_ARMOUR:
267 type = OBJ_ARMOUR;
268 break;
269 case ITEM_JEWELLERY:
270 type = OBJ_JEWELLERY;
271 break;
272 case ITEM_MISCELLANY:
273 type = OBJ_MISCELLANY;
274 break;
275 case ITEM_MANUALS:
276 case ITEM_SPELLBOOKS:
277 type = OBJ_BOOKS;
278 break;
279 default:
280 type = OBJ_UNASSIGNED;
281 break;
282 }
283 return type;
284 }
285
_item_class_name(item_base_type base_type)286 static string _item_class_name(item_base_type base_type)
287 {
288 string name;
289 switch (base_type)
290 {
291 case ITEM_WEAPONS:
292 name = "Weapons";
293 break;
294 case ITEM_SPELLBOOKS:
295 name = "Spellbooks";
296 break;
297 case ITEM_MANUALS:
298 name = "Manuals";
299 break;
300 default:
301 name = item_class_name(_item_orig_base_type(base_type));
302 }
303 return name;
304 }
305
_item_orig_sub_type(item_base_type base_type,int sub_type)306 static int _item_orig_sub_type(item_base_type base_type, int sub_type)
307 {
308 int type;
309 switch (base_type)
310 {
311 case ITEM_MANUALS:
312 type = BOOK_MANUAL;
313 break;
314 default:
315 type = sub_type;
316 break;
317 }
318 return type;
319 }
320
_item_max_sub_type(item_base_type base_type)321 static int _item_max_sub_type(item_base_type base_type)
322 {
323 int num = 0;
324 switch (base_type)
325 {
326 case ITEM_MANUALS:
327 num = NUM_SKILLS;
328 break;
329 default:
330 num = get_max_subtype(_item_orig_base_type(base_type));
331 break;
332 }
333 return num;
334 }
335
_item_tracks_artefact(item_base_type base_type)336 static bool _item_tracks_artefact(item_base_type base_type)
337 {
338 switch (base_type)
339 {
340 case ITEM_WEAPONS:
341 case ITEM_ARMOUR:
342 case ITEM_JEWELLERY:
343 case ITEM_SPELLBOOKS:
344 return true;
345 default:
346 return false;
347 }
348 }
349
_item_tracks_plus(item_base_type base_type)350 static bool _item_tracks_plus(item_base_type base_type)
351 {
352 switch (base_type)
353 {
354 case ITEM_WEAPONS:
355 case ITEM_ARMOUR:
356 case ITEM_JEWELLERY:
357 case ITEM_WANDS:
358 case ITEM_MISCELLANY:
359 return true;
360 default:
361 return false;
362 }
363 }
364
_item_tracks_brand(item_base_type base_type)365 static bool _item_tracks_brand(item_base_type base_type)
366 {
367 switch (base_type)
368 {
369 case ITEM_WEAPONS:
370 case ITEM_ARMOUR:
371 case ITEM_MISSILES:
372 return true;
373 default:
374 return false;
375 }
376 }
377
_item_tracks_monster(item_base_type base_type)378 static bool _item_tracks_monster(item_base_type base_type)
379 {
380 switch (base_type)
381 {
382 case ITEM_MISCELLANY:
383 case ITEM_SPELLBOOKS:
384 case ITEM_MANUALS:
385 return false;
386 default:
387 return true;
388 }
389 }
390
_item_tracks_shop(item_base_type base_type)391 static bool _item_tracks_shop(item_base_type base_type)
392 {
393 switch (base_type)
394 {
395 case ITEM_GOLD:
396 return false;
397 default:
398 return true;
399 }
400 }
401
_dummy_item(item_base_type base_type,int sub_type,int brand=0)402 static item_def _dummy_item(item_base_type base_type, int sub_type,
403 int brand = 0)
404 {
405 item_def dummy_item;
406
407 dummy_item.base_type = _item_orig_base_type(base_type);
408 if (sub_type == _item_max_sub_type(base_type))
409 dummy_item.sub_type = 0;
410 else
411 dummy_item.sub_type = _item_orig_sub_type(base_type, sub_type);
412
413 dummy_item.brand = brand;
414
415 if (base_type == ITEM_MANUALS)
416 dummy_item.skill = static_cast<skill_type>(sub_type);
417
418 dummy_item.quantity = 1;
419
420 return dummy_item;
421 }
422
_brand_name(item_base_type base_type,int sub_type,int brand)423 static string _brand_name(item_base_type base_type, int sub_type, int brand)
424 {
425 if (!brand)
426 return "none";
427
428 string brand_name = "";
429 const item_def dummy_item = _dummy_item(base_type, sub_type, brand);
430 switch (base_type)
431 {
432 case ITEM_WEAPONS:
433 brand_name = weapon_brand_name(dummy_item, true);
434 break;
435 case ITEM_ARMOUR:
436 brand_name = armour_ego_name(dummy_item, true);
437 break;
438 case ITEM_MISSILES:
439 brand_name = missile_brand_name(dummy_item, MBN_TERSE);
440 break;
441 default:
442 break;
443 }
444 return brand_name;
445 }
446
_item_name(item_base_type base_type,int sub_type)447 static string _item_name(item_base_type base_type, int sub_type)
448 {
449 string name = "";
450 description_level_type desc_type = DESC_DBNAME;
451
452 // These types need special handling, otherwise we use the original
453 // item_def name.
454 if (sub_type == _item_max_sub_type(base_type))
455 name = "All " + _item_class_name(base_type);
456 else if (base_type == ITEM_SPELLBOOKS)
457 {
458 int orig_type = _item_orig_sub_type(base_type, sub_type);
459 if (orig_type == BOOK_RANDART_LEVEL)
460 name = "Level Artefact Book";
461 else if (orig_type == BOOK_RANDART_LEVEL)
462 name = "Theme Artefact Book";
463 }
464 else if (base_type == ITEM_MANUALS)
465 desc_type = DESC_QUALNAME;
466
467 if (name.empty())
468 {
469 const item_def item = _dummy_item(base_type, sub_type);
470 name = item.name(desc_type, true, true);
471 }
472 return name;
473 }
474
_level_name(const level_id & lev)475 static string _level_name(const level_id &lev)
476 {
477 string name;
478 if (lev.branch == NUM_BRANCHES)
479 name = "AllLevels";
480 else if (lev.depth == -1 || brdepth[lev.branch] == 1)
481 name = lev.describe(false, false);
482 else if (brdepth[lev.branch] < 10)
483 name = lev.describe(false, true);
484 else
485 {
486 name = make_stringf("%s:%02d", lev.describe(false, false).c_str(),
487 lev.depth);
488 }
489 return name;
490 }
491
objstat_item(const item_def & item)492 objstat_item::objstat_item(const item_def &item)
493 {
494 base_type = _item_base_type(item);
495
496 if (base_type == ITEM_MANUALS)
497 sub_type = item.skill;
498 else
499 sub_type = item.sub_type;
500
501 quantity = item.quantity;
502 plus = item.plus;
503
504 is_arte = is_artefact(item);
505
506 // The item's position won't be valid for these two first cases, as it's
507 // set to a special indicator value.
508 if (item.holding_monster())
509 {
510 in_vault = map_masked(item.holding_monster()->pos(), MMT_VAULT);
511 held_mons = true;
512 in_shop = false;
513 }
514 else if (is_shop_item(item))
515 {
516 // XXX I don't think shops can place outside of vaults, but it would be
517 // nice to find the item's true location, so we wouldn't have to
518 // assume. -gammafunk
519 in_vault = true;
520 held_mons = false;
521 in_shop = true;
522 }
523 else
524 {
525 in_vault = map_masked(item.pos, MMT_VAULT);
526 held_mons = false;
527 in_shop = false;
528 }
529
530 brand = item.brand;
531 if (base_type == ITEM_MISSILES)
532 brand = get_ammo_brand(item);
533 else if (base_type == ITEM_WEAPONS)
534 brand = get_weapon_brand(item);
535 else if (base_type == ITEM_ARMOUR)
536 brand = get_armour_ego_type(item);
537
538 if (base_type == ITEM_SPELLBOOKS)
539 spells = spells_in_book(item);
540 }
541
_init_monsters()542 static void _init_monsters()
543 {
544 for (int i = 0; i < NUM_MONSTERS; i++)
545 {
546 const monster_type mc = static_cast<monster_type>(i);
547
548 if (mons_class_gives_xp(mc) && !mons_class_flag(mc, M_CANT_SPAWN))
549 objstat_monsters.insert(mc);
550 }
551 // For the all-monsters summary
552 objstat_monsters.insert(NUM_MONSTERS);
553 }
554
_init_features()555 static void _init_features()
556 {
557 for (int i = 0; i < NUM_FEATURES; i++)
558 {
559 const dungeon_feature_type feat = static_cast<dungeon_feature_type>(i);
560
561 if (is_valid_feature_type(feat))
562 objstat_features.insert(feat);
563 }
564 }
565
_init_spells()566 static void _init_spells()
567 {
568 for (int i = 0; i < NUM_SPELLS; i++)
569 {
570 const auto spell = static_cast<spell_type>(i);
571
572 if (is_valid_spell(spell) && is_player_book_spell(spell))
573 objstat_spells.insert(spell);
574 }
575 // For the all-spells summary
576 objstat_spells.insert(NUM_SPELLS);
577 }
578
579 // Initialize field data that needs non-default intialization (i.e. to
580 // something other than zero). Handling this in one pass creates a lot of
581 // potentially unused entries, but it's better than trying to guard against
582 // default initialization everywhere. For the rest of the fields, it's fine to
583 // default initialize to zero whenever they're first referenced.
_init_stats()584 static void _init_stats()
585 {
586 for (const auto &level : stat_levels)
587 {
588 for (int i = 0; i < NUM_ITEM_BASE_TYPES; i++)
589 {
590 const item_base_type base_type = static_cast<item_base_type>(i);
591
592 int num_entries = _item_max_sub_type(base_type);
593 // An additional entry for the across-subtype summary if there's
594 // more than one subtype.
595 num_entries += num_entries > 1;
596 for (int j = 0; j < num_entries; j++)
597 {
598 item_recs[level][base_type][j]["NumMin"] = INT_MAX;
599 item_recs[level][base_type][j]["NumMax"] = -1;
600 }
601 }
602
603 for (auto mc : objstat_monsters)
604 {
605 monster_recs[level][mc]["NumMin"] = INT_MAX;
606 monster_recs[level][mc]["NumMax"] = -1;
607 }
608
609 for (auto feat : objstat_features)
610 {
611 feature_recs[level][feat]["NumMin"] = INT_MAX;
612 feature_recs[level][feat]["NumMax"] = -1;
613 }
614
615 for (auto spell : objstat_spells)
616 {
617 spell_recs[level][spell]["NumMin"] = INT_MAX;
618 spell_recs[level][spell]["NumMax"] = -1;
619 }
620 }
621 }
622
_record_item_stat(const objstat_item & item,string field,int value)623 static void _record_item_stat(const objstat_item &item, string field, int value)
624 {
625 const level_id cur_lev = level_id::current();
626
627 bool need_all = false;
628 if (stat_levels.count(all_lev))
629 need_all = true;
630
631 const level_id br_lev = level_id(cur_lev.branch, -1);
632 bool need_branch = false;
633 if (stat_levels.count(br_lev))
634 need_branch = true;
635
636 item_recs[cur_lev][item.base_type][item.sub_type][field] += value;
637
638 if (need_all)
639 item_recs[all_lev][item.base_type][item.sub_type][field] += value;
640
641 if (need_branch)
642 item_recs[br_lev][item.base_type][item.sub_type][field] += value;
643
644 // Record a class summary if more than one subtype exists.
645 const int class_sum = _item_max_sub_type(item.base_type);
646 if (class_sum > 1)
647 {
648 item_recs[cur_lev][item.base_type][class_sum][field] += value;
649
650 if (need_all)
651 item_recs[all_lev][item.base_type][class_sum][field] += value;
652
653 if (need_branch)
654 item_recs[br_lev][item.base_type][class_sum][field] += value;
655 }
656 }
657
_record_item_brand_category(const objstat_item & item,stat_category_type cat)658 static void _record_item_brand_category(const objstat_item &item,
659 stat_category_type cat)
660 {
661 const level_id cur_lev = level_id::current();
662
663 bool need_all = false;
664 if (stat_levels.count(all_lev))
665 need_all = true;
666
667 const level_id br_lev = level_id(cur_lev.branch, -1);
668 bool need_branch = false;
669 if (stat_levels.count(br_lev))
670 need_branch = true;
671
672 brand_recs[cur_lev][item.base_type][item.sub_type][cat][item.brand] +=
673 item.quantity;
674
675 if (need_all)
676 {
677 brand_recs[all_lev][item.base_type][item.sub_type][cat][item.brand] +=
678 item.quantity;
679 }
680
681 if (need_branch)
682 {
683 brand_recs[br_lev][item.base_type][item.sub_type][cat][item.brand] +=
684 item.quantity;
685 }
686
687 // Record a class summary if more than one subtype exists.
688 const int cls = _item_max_sub_type(item.base_type);
689 if (cls > 1)
690 {
691 brand_recs[cur_lev][item.base_type][cls][cat][item.brand] +=
692 item.quantity;
693
694 if (need_all)
695 {
696 brand_recs[all_lev][item.base_type][cls][cat][item.brand] +=
697 item.quantity;
698 }
699
700 if (need_branch)
701 {
702 brand_recs[br_lev][item.base_type][cls][cat][item.brand] +=
703 item.quantity;
704 }
705 }
706 }
707
_record_item_brand(const objstat_item & item)708 static void _record_item_brand(const objstat_item &item)
709 {
710 ASSERT(_item_tracks_brand(item.base_type));
711
712 _record_item_brand_category(item, CATEGORY_ALL);
713
714 if (item.in_vault)
715 _record_item_brand_category(item, CATEGORY_VAULT);
716
717 if (item.is_arte)
718 _record_item_brand_category(item, CATEGORY_ARTEFACT);
719
720 if (item.in_shop)
721 _record_item_brand_category(item, CATEGORY_SHOP);
722
723 if (item.held_mons)
724 _record_item_brand_category(item, CATEGORY_MONSTER);
725 }
726
_record_spell_stat(spell_type spell,string field,int value)727 static void _record_spell_stat(spell_type spell, string field, int value)
728 {
729 const level_id cur_lev = level_id::current();
730
731 bool need_all = false;
732 if (stat_levels.count(all_lev))
733 need_all = true;
734
735 const level_id br_lev = level_id(cur_lev.branch, -1);
736 bool need_branch = false;
737 if (stat_levels.count(br_lev))
738 need_branch = true;
739
740 spell_recs[cur_lev][spell][field] += value;
741 spell_recs[cur_lev][NUM_SPELLS][field] += value;
742
743 if (need_all)
744 {
745 spell_recs[all_lev][spell][field] += value;
746 spell_recs[all_lev][NUM_SPELLS][field] += value;
747 }
748
749 if (need_branch)
750 {
751 spell_recs[br_lev][spell][field] += value;
752 spell_recs[br_lev][NUM_SPELLS][field] += value;
753 }
754 }
755
_record_book_spells(const objstat_item & item)756 static void _record_book_spells(const objstat_item &item)
757 {
758 for (auto spell : item.spells)
759 {
760 _record_spell_stat(spell, "Num", 1);
761 _record_spell_stat(spell, "NumForIter", 1);
762
763 if (item.in_vault)
764 {
765 _record_spell_stat(spell, "NumVault", 1);
766 _record_spell_stat(spell, "NumForIterVault", 1);
767 }
768
769 if (item.is_arte)
770 {
771 _record_spell_stat(spell, "NumArte", 1);
772 _record_spell_stat(spell, "NumForIterArte", 1);
773 }
774
775 if (item.in_shop)
776 {
777 _record_spell_stat(spell, "NumShop", 1);
778 _record_spell_stat(spell, "NumForIterShop", 1);
779 }
780 }
781 }
782
objstat_record_item(const item_def & item)783 void objstat_record_item(const item_def &item)
784 {
785 const objstat_item objs_item(item);
786
787 // Just in case, don't count mimics as items; these are converted
788 // explicitely in mg_do_build_level().
789 if (item.flags & ISFLAG_MIMIC || objs_item.base_type == ITEM_IGNORE)
790 return;
791
792 const bool track_plus = _item_tracks_plus(objs_item.base_type);
793 string plus_field = "Ench";
794 if (objs_item.base_type == ITEM_WANDS)
795 plus_field = "Chrg";
796
797 _record_item_stat(objs_item, "Num", objs_item.quantity);
798 _record_item_stat(objs_item, "NumForIter", objs_item.quantity);
799
800 if (track_plus)
801 _record_item_stat(objs_item, plus_field, objs_item.plus);
802
803 if (objs_item.in_vault)
804 {
805 _record_item_stat(objs_item, "NumVault", objs_item.quantity);
806
807 if (track_plus)
808 _record_item_stat(objs_item, plus_field + "Vault", objs_item.plus);
809 }
810
811 if (_item_tracks_artefact(objs_item.base_type) && objs_item.is_arte)
812 {
813 _record_item_stat(objs_item, "NumArte", objs_item.quantity);
814
815 if (track_plus)
816 _record_item_stat(objs_item, plus_field + "Arte", objs_item.plus);
817 }
818
819 if (_item_tracks_brand(objs_item.base_type) && objs_item.brand > 0)
820 {
821 _record_item_stat(objs_item, "NumBrand", objs_item.quantity);
822
823 if (track_plus)
824 _record_item_stat(objs_item, plus_field + "Brand", objs_item.plus);
825
826 _record_item_brand(objs_item);
827 }
828
829 if (_item_tracks_shop(objs_item.base_type) && objs_item.in_shop)
830 {
831 _record_item_stat(objs_item, "NumShop", objs_item.quantity);
832
833 if (track_plus)
834 _record_item_stat(objs_item, plus_field + "Shop", objs_item.plus);
835 }
836
837 if (_item_tracks_monster(objs_item.base_type) && objs_item.held_mons)
838 {
839 _record_item_stat(objs_item, "NumMons", objs_item.quantity);
840
841 if (track_plus)
842 _record_item_stat(objs_item, plus_field + "Mons", objs_item.plus);
843 }
844
845 _record_book_spells(objs_item);
846 }
847
_record_monster_stat(monster_type mc,string field,int value)848 static void _record_monster_stat(monster_type mc, string field, int value)
849 {
850 const level_id cur_lev = level_id::current();
851
852 bool need_all = false;
853 if (stat_levels.count(all_lev))
854 need_all = true;
855
856 const level_id br_lev = level_id(cur_lev.branch, -1);
857 bool need_branch = false;
858 if (stat_levels.count(br_lev))
859 need_branch = true;
860
861 monster_recs[cur_lev][mc][field] += value;
862 monster_recs[cur_lev][NUM_MONSTERS][field] += value;
863
864 if (need_all)
865 {
866 monster_recs[all_lev][mc][field] += value;
867 monster_recs[all_lev][NUM_MONSTERS][field] += value;
868 }
869
870 if (need_branch)
871 {
872 monster_recs[br_lev][mc][field] += value;
873 monster_recs[br_lev][NUM_MONSTERS][field] += value;
874 }
875 }
876
objstat_record_monster(const monster * mons)877 void objstat_record_monster(const monster *mons)
878 {
879 monster_type type;
880 if (mons->has_ench(ENCH_GLOWING_SHAPESHIFTER))
881 type = MONS_GLOWING_SHAPESHIFTER;
882 else if (mons->has_ench(ENCH_SHAPESHIFTER))
883 type = MONS_SHAPESHIFTER;
884 else
885 type = mons->type;
886
887 if (!objstat_monsters.count(type))
888 return;
889
890 _record_monster_stat(type, "Num", 1);
891 _record_monster_stat(type, "NumForIter", 1);
892 _record_monster_stat(type, "MonsXP", exper_value(*mons));
893 _record_monster_stat(type, "TotalXP", exper_value(*mons));
894 _record_monster_stat(type, "MonsHP", mons->max_hit_points);
895 _record_monster_stat(type, "MonsHD", mons->get_experience_level());
896
897 if (!mons->originating_map().empty())
898 {
899 _record_monster_stat(type, "NumVault", 1);
900 _record_monster_stat(type, "TotalXPVault", exper_value(*mons));
901 }
902 }
903
_record_feature_stat(dungeon_feature_type feat_type,string field,int value)904 static void _record_feature_stat(dungeon_feature_type feat_type, string field,
905 int value)
906 {
907 const level_id cur_lev = level_id::current();
908
909 bool need_all = false;
910 if (stat_levels.count(all_lev))
911 need_all = true;
912
913 const level_id br_lev = level_id(cur_lev.branch, -1);
914 bool need_branch = false;
915 if (stat_levels.count(br_lev))
916 need_branch = true;
917
918 feature_recs[cur_lev][feat_type][field] += value;
919
920 if (need_all)
921 feature_recs[all_lev][feat_type][field] += value;
922
923 if (need_branch)
924 feature_recs[br_lev][feat_type][field] += value;
925 }
926
objstat_record_feature(dungeon_feature_type feat,bool in_vault)927 void objstat_record_feature(dungeon_feature_type feat, bool in_vault)
928 {
929 if (!objstat_features.count(feat))
930 return;
931
932 _record_feature_stat(feat, "Num", 1);
933 _record_feature_stat(feat, "NumForIter", 1);
934
935 if (in_vault)
936 _record_feature_stat(feat, "NumVault", 1);
937 }
938
objstat_iteration_stats()939 void objstat_iteration_stats()
940 {
941 for (const auto level : stat_levels)
942 {
943 for (int i = 0; i < NUM_ITEM_BASE_TYPES; i++)
944 {
945 item_base_type base_type = static_cast<item_base_type>(i);
946
947 int num_entries = _item_max_sub_type(base_type);
948 num_entries = num_entries == 1 ? 1 : num_entries + 1;
949 for (int j = 0; j < num_entries ; j++)
950 {
951 map<string, int> &stats = item_recs[level][base_type][j];
952
953 if (stats["NumForIter"] > stats["NumMax"])
954 stats["NumMax"] = stats["NumForIter"];
955
956 if (stats["NumForIter"] < stats["NumMin"])
957 stats["NumMin"] = stats["NumForIter"];
958
959 stats["NumSD"] += stats["NumForIter"] * stats["NumForIter"];
960
961 stats["NumForIter"] = 0;
962 }
963 }
964
965 for (auto spell : objstat_spells)
966 {
967 map<string, int> &stats = spell_recs[level][spell];
968
969 if (stats["NumForIter"] > 0)
970 stats["Chance"] += 1;
971
972 if (stats["NumForIter"] > stats["NumMax"])
973 stats["NumMax"] = stats["NumForIter"];
974
975 if (stats["NumForIter"] < stats["NumMin"])
976 stats["NumMin"] = stats["NumForIter"];
977
978 stats["NumSD"] += stats["NumForIter"] * stats["NumForIter"];
979
980 stats["NumForIter"] = 0;
981
982 if (stats["NumForIterVault"])
983 stats["ChanceVault"] += 1;
984 stats["NumForIterVault"] = 0;
985
986 if (stats["NumForIterArte"])
987 stats["ChanceArte"] += 1;
988 stats["NumForIterArte"] = 0;
989
990 if (stats["NumForIterShop"])
991 stats["ChanceShop"] += 1;
992 stats["NumForIterShop"] = 0;
993 }
994
995 for (auto mc : objstat_monsters)
996 {
997 map<string, int> &stats = monster_recs[level][mc];
998
999 if (stats["NumForIter"] > stats["NumMax"])
1000 stats["NumMax"] = stats["NumForIter"];
1001
1002 if (stats["NumForIter"] < stats["NumMin"])
1003 stats["NumMin"] = stats["NumForIter"];
1004
1005 stats["NumSD"] += stats["NumForIter"] * stats["NumForIter"];
1006
1007 stats["NumForIter"] = 0;
1008 }
1009
1010 for (auto feat : objstat_features)
1011 {
1012 map<string, int> &stats = feature_recs[level][feat];
1013
1014 if (stats["NumForIter"] > stats["NumMax"])
1015 stats["NumMax"] = stats["NumForIter"];
1016
1017 if (stats["NumForIter"] < stats["NumMin"])
1018 stats["NumMin"] = stats["NumForIter"];
1019
1020 stats["NumSD"] += stats["NumForIter"] * stats["NumForIter"];
1021
1022 stats["NumForIter"] = 0;
1023 }
1024 }
1025 }
1026
_open_stat_file(string stat_file)1027 static FILE * _open_stat_file(string stat_file)
1028 {
1029 FILE *stat_fh = nullptr;
1030 stat_fh = fopen(stat_file.c_str(), "w");
1031
1032 if (!stat_fh)
1033 {
1034 end(1, false, "Unable to open objstat output file: %s\n"
1035 "Error: %s", stat_file.c_str(), strerror(errno));
1036 }
1037
1038 return stat_fh;
1039 }
1040
_write_stat_info()1041 static void _write_stat_info()
1042 {
1043 string all_desc;
1044 if (num_branches > 1)
1045 {
1046 if (SysEnv.map_gen_range)
1047 all_desc = SysEnv.map_gen_range->describe();
1048 else
1049 all_desc = "All Levels";
1050 all_desc = "Levels included in AllLevels: " + all_desc + "\n";
1051 }
1052
1053 const string out_file = make_stringf("%s%s%s", stat_out_prefix,
1054 "Info", stat_out_ext);
1055 stat_outf = _open_stat_file(out_file.c_str());
1056
1057 fprintf(stat_outf, "Object Generation Stats\n"
1058 "Number of iterations: %d\n"
1059 "Number of branches: %d\n"
1060 "%s"
1061 "Number of levels: %d\n"
1062 "Version: %s\n", SysEnv.map_gen_iters, num_branches,
1063 all_desc.c_str(), num_levels, Version::Long);
1064
1065 fclose(stat_outf);
1066 printf("Wrote Objstat Info to %s.\n", out_file.c_str());
1067 }
1068
_write_stat_headers(const vector<string> & fields,const string & desc)1069 static void _write_stat_headers(const vector<string> &fields, const string &desc)
1070 {
1071 fprintf(stat_outf, "%s\tLevel", desc.c_str());
1072
1073 for (const string &field : fields)
1074 fprintf(stat_outf, "\t%s", field.c_str());
1075
1076 fprintf(stat_outf, "\n");
1077 }
1078
_write_stat(map<string,int> & stats,const string & field)1079 static void _write_stat(map<string, int> &stats, const string &field)
1080 {
1081 const double field_val = stats[field];
1082 double out_val = 0;
1083 ostringstream output;
1084 bool is_chance = false;
1085
1086 output.precision(STAT_PRECISION);
1087 output.setf(ios_base::fixed);
1088
1089 // These fields want a per-instance average.
1090 if (starts_with(field, "Ench")
1091 || starts_with(field, "Chrg")
1092 || field == "MonsHD"
1093 || field == "MonsHP"
1094 || field == "MonsXP")
1095 {
1096 out_val = field_val / stats["Num"];
1097 }
1098 // Turn the sum of squares into the standard deviation.
1099 else if (field == "NumSD")
1100 {
1101 if (SysEnv.map_gen_iters == 1)
1102 out_val = 0;
1103 else
1104 {
1105 const double mean = (double) stats["Num"] / SysEnv.map_gen_iters;
1106 out_val = sqrt((SysEnv.map_gen_iters / (SysEnv.map_gen_iters - 1.0))
1107 * (field_val / SysEnv.map_gen_iters - mean * mean));
1108 }
1109 }
1110 else if (ends_with(field, "Min") || ends_with(field, "Max"))
1111 out_val = field_val;
1112 // Turn a probability into a chance percentage.
1113 else if (field.find("Chance") == 0)
1114 {
1115 out_val = 100 * field_val / SysEnv.map_gen_iters;
1116 is_chance = true;
1117 }
1118 else
1119 out_val = field_val / SysEnv.map_gen_iters;
1120
1121 output << "\t" << out_val;
1122
1123 if (is_chance)
1124 output << "%";
1125
1126 fprintf(stat_outf, "%s", output.str().c_str());
1127 }
1128
_write_level_item_brand_stats(const level_id & level,item_base_type base_type,int sub_type)1129 static void _write_level_item_brand_stats(const level_id &level,
1130 item_base_type base_type, int sub_type)
1131 {
1132 for (int i = 0; i < NUM_STAT_CATEGORIES; i++)
1133 {
1134 bool first_brand = true;
1135
1136 ostringstream brand_summary;
1137 brand_summary.setf(ios_base::fixed);
1138 brand_summary.precision(STAT_PRECISION);
1139
1140 const auto cat = static_cast<stat_category_type>(i);
1141
1142 for (auto mentry : brand_recs[level][base_type][sub_type][cat])
1143 {
1144 // No instances for this brand.
1145 if (mentry.second == 0)
1146 continue;
1147
1148 const double value = (double) mentry.second / SysEnv.map_gen_iters;
1149
1150 if (first_brand)
1151 first_brand = false;
1152 else
1153 brand_summary << ";";
1154
1155 string brand_name = _brand_name(base_type, sub_type, mentry.first);
1156 brand_summary << brand_name.c_str() << ":" << value;
1157 }
1158
1159 fprintf(stat_outf, "\t%s", brand_summary.str().c_str());
1160 }
1161 }
1162
_write_level_item_stats(const level_id & level,item_base_type base_type,int sub_type)1163 static void _write_level_item_stats(const level_id &level,
1164 item_base_type base_type, int sub_type)
1165 {
1166 // Don't print stats if no instances.
1167 if (item_recs[level][base_type][sub_type]["Num"] < 1)
1168 return;
1169
1170 fprintf(stat_outf, "%s\t%s", _item_name(base_type, sub_type).c_str(),
1171 _level_name(level).c_str());
1172
1173 for (const auto &field : item_fields[base_type])
1174 _write_stat(item_recs[level][base_type][sub_type], field);
1175
1176 if (_item_tracks_brand(base_type))
1177 _write_level_item_brand_stats(level, base_type, sub_type);
1178
1179 fprintf(stat_outf, "\n");
1180 }
1181
_write_base_item_stats(item_base_type base_type)1182 static void _write_base_item_stats(item_base_type base_type)
1183 {
1184 const string out_file = make_stringf("%s%s%s", stat_out_prefix,
1185 strip_filename_unsafe_chars(_item_class_name(base_type)).c_str(),
1186 stat_out_ext);
1187 stat_outf = _open_stat_file(out_file.c_str());
1188
1189 vector<string> fields = item_fields[base_type];
1190
1191 if (_item_tracks_brand(base_type))
1192 {
1193 fields.insert(fields.end(), brand_fields[base_type].begin(),
1194 brand_fields[base_type].end());
1195 }
1196
1197 _write_stat_headers(fields, "Item");
1198
1199 // If there is more than one subtype, we have an additional entry for
1200 // the sum across subtypes.
1201 const int num_types = _item_max_sub_type(base_type);
1202 int num_entries = num_types == 1 ? 1 : num_types + 1;
1203 for (int j = 0; j < num_entries; j++)
1204 for (const auto &level : stat_levels)
1205 _write_level_item_stats(level, base_type, j);
1206
1207 fclose(stat_outf);
1208 printf("Wrote %s item stats to %s.\n", _item_class_name(base_type).c_str(),
1209 out_file.c_str());
1210 }
1211
_write_monster_stats(monster_type mc)1212 static void _write_monster_stats(monster_type mc)
1213 {
1214 string mons_name;
1215 if (mc == NUM_MONSTERS)
1216 mons_name = "All Monsters";
1217 else
1218 mons_name = mons_type_name(mc, DESC_PLAIN);
1219
1220 for (const level_id &level: stat_levels)
1221 {
1222 if (monster_recs[level][mc]["Num"] < 1)
1223 continue;
1224
1225 fprintf(stat_outf, "%s\t%s", mons_name.c_str(),
1226 _level_name(level).c_str());
1227
1228 for (const string &field : monster_fields)
1229 _write_stat(monster_recs[level][mc], field);
1230
1231 fprintf(stat_outf, "\n");
1232 }
1233 }
1234
_write_feature_stats(dungeon_feature_type feat_type)1235 static void _write_feature_stats(dungeon_feature_type feat_type)
1236 {
1237 const char *feat_name = get_feature_def(feat_type).name;
1238
1239 for (const level_id &level : stat_levels)
1240 {
1241 if (feature_recs[level][feat_type]["Num"] < 1)
1242 continue;
1243
1244 fprintf(stat_outf, "%s\t%s", feat_name, _level_name(level).c_str());
1245
1246 for (const string &field : feature_fields)
1247 _write_stat(feature_recs[level][feat_type], field);
1248
1249 fprintf(stat_outf, "\n");
1250 }
1251 }
1252
_write_spell_stats(spell_type spell)1253 static void _write_spell_stats(spell_type spell)
1254 {
1255 string spell_name;
1256 if (spell == NUM_SPELLS)
1257 spell_name = "All Spells";
1258 else
1259 spell_name = spell_title(spell);
1260
1261 for (const level_id &level : stat_levels)
1262 {
1263 if (spell_recs[level][spell]["Num"] < 1)
1264 continue;
1265
1266 fprintf(stat_outf, "%s\t%s", spell_name.c_str(),
1267 _level_name(level).c_str());
1268
1269 for (const string &field : spell_fields)
1270 _write_stat(spell_recs[level][spell], field);
1271
1272 fprintf(stat_outf, "\n");
1273 }
1274 }
1275
_write_object_stats()1276 static void _write_object_stats()
1277 {
1278 _write_stat_info();
1279
1280 for (int i = 0; i < NUM_ITEM_BASE_TYPES; i++)
1281 {
1282 item_base_type base_type = static_cast<item_base_type>(i);
1283 _write_base_item_stats(base_type);
1284 }
1285
1286 string out_file = make_stringf("%s%s%s", stat_out_prefix, "Spells",
1287 stat_out_ext);
1288 stat_outf = _open_stat_file(out_file.c_str());
1289
1290 _write_stat_headers(spell_fields, "Spell");
1291 for (const auto spell : objstat_spells)
1292 _write_spell_stats(spell);
1293
1294 fclose(stat_outf);
1295 printf("Wrote Spell stats to %s.\n", out_file.c_str());
1296
1297 out_file = make_stringf("%s%s%s", stat_out_prefix, "Monsters",
1298 stat_out_ext);
1299 stat_outf = _open_stat_file(out_file.c_str());
1300
1301 _write_stat_headers(monster_fields, "Monster");
1302 for (const auto mc : objstat_monsters)
1303 _write_monster_stats(mc);
1304
1305 fclose(stat_outf);
1306 printf("Wrote Monster stats to %s.\n", out_file.c_str());
1307
1308 out_file = make_stringf("%s%s%s", stat_out_prefix, "Features",
1309 stat_out_ext);
1310 stat_outf = _open_stat_file(out_file.c_str());
1311
1312 _write_stat_headers(feature_fields, "Feature");
1313 for (auto feat : objstat_features)
1314 _write_feature_stats(feat);
1315
1316 fclose(stat_outf);
1317 printf("Wrote Feature stats to %s.\n", out_file.c_str());
1318 }
1319
objstat_generate_stats()1320 void objstat_generate_stats()
1321 {
1322 // Warn assertions about possible oddities like the artefact list being
1323 // cleared.
1324 you.wizard = true;
1325 // Let "acquire foo" have skill aptitudes to work with.
1326 you.species = SP_HUMAN;
1327
1328 if (!crawl_state.force_map.empty() && !mapstat_find_forced_map())
1329 return;
1330
1331 initialise_item_descriptions();
1332 initialise_branch_depths();
1333
1334 // We have to run map preludes ourselves.
1335 run_map_global_preludes();
1336 run_map_local_preludes();
1337
1338 // Populate a vector of the levels ids for levels we're tabulating.
1339 for (branch_iterator it; it; ++it)
1340 {
1341 if (brdepth[it->id] == -1)
1342 continue;
1343
1344 const branch_type br = it->id;
1345 #if TAG_MAJOR_VERSION == 34
1346 // Don't want to include Forest since it doesn't generate
1347 if (br == BRANCH_FOREST)
1348 continue;
1349 #endif
1350 vector<level_id> levels;
1351 int branch_level_count = 0;
1352 for (int dep = 1; dep <= brdepth[br]; ++dep)
1353 {
1354 const level_id lid(br, dep);
1355 if (SysEnv.map_gen_range
1356 && !SysEnv.map_gen_range->is_usable_in(lid))
1357 {
1358 continue;
1359 }
1360 stat_levels.insert(lid);
1361 ++branch_level_count;
1362 ++num_levels;
1363 }
1364
1365 if (branch_level_count > 0)
1366 {
1367 ++num_branches;
1368 // Multiple levels for this branch, so we do a branch-wise summary.
1369 if (branch_level_count > 1)
1370 stat_levels.insert(level_id(br, -1));
1371
1372 }
1373 }
1374
1375 // If there's only one branch, an all-levels summary isn't necessary.
1376 if (num_branches > 1)
1377 stat_levels.insert(all_lev);
1378
1379 printf("Generating object statistics for %d iteration(s) of %d "
1380 "level(s) over %d branch(es).\n", SysEnv.map_gen_iters,
1381 num_levels, num_branches);
1382
1383 _init_spells();
1384 _init_features();
1385 _init_monsters();
1386
1387 _init_stats();
1388
1389 if (mapstat_build_levels())
1390 {
1391 _write_object_stats();
1392 printf("Object statistics complete.\n");
1393 }
1394 }
1395 #endif // DEBUG_STATISTICS
1396