/** * @file * @brief Debugging code to scan the list of items and monsters. **/ #include "AppHdr.h" #include "dbg-scan.h" #include #include #include #include "artefact.h" #include "branch.h" #include "butcher.h" #include "chardump.h" #include "coord.h" #include "coordit.h" #include "dbg-maps.h" #include "dbg-util.h" #include "decks.h" #include "dungeon.h" #include "end.h" #include "env.h" #include "godabil.h" #include "items.h" #include "itemname.h" #include "itemprop.h" #include "initfile.h" #include "invent.h" #include "libutil.h" #include "maps.h" #include "message.h" #include "mon-util.h" #include "ng-init.h" #include "shopping.h" #include "state.h" #include "strings.h" #include "stepdown.h" #include "terrain.h" #include "traps.h" #include "version.h" #define DEBUG_ITEM_SCAN #ifdef DEBUG_ITEM_SCAN static void _dump_item(const char *name, int num, const item_def &item, PRINTF(3, )); static void _dump_item(const char *name, int num, const item_def &item, const char *format, ...) { #ifdef DEBUG_FATAL const msg_channel_type chan = MSGCH_WARN; #else const msg_channel_type chan = MSGCH_ERROR; #endif va_list args; va_start(args, format); string msg = vmake_stringf(format, args); va_end(args); mprf(chan, "%s", msg.c_str()); mprf(chan, "%s", name); mprf(" item #%d: base: %d; sub: %d; plus: %d; plus2: %d; special: %d", num, item.base_type, item.sub_type, item.plus, item.plus2, item.special); mprf(" quant: %d; colour: %d; ident: 0x%08" PRIx32"; ident_type: %d", item.quantity, (int)item.colour, item.flags, get_ident_type(item)); mprf(" x: %d; y: %d; link: %d", item.pos.x, item.pos.y, item.link); #ifdef DEBUG_FATAL if (!crawl_state.game_crashed) die("%s %s", msg.c_str(), name); #endif crawl_state.cancel_cmd_repeat(); } //--------------------------------------------------------------- // // debug_item_scan // //--------------------------------------------------------------- void debug_item_scan() { int i; char name[256]; FixedBitVector visited; // First we're going to check all the stacks on the level: for (rectangle_iterator ri(0); ri; ++ri) { // Unlinked temporary items. if (*ri == coord_def()) continue; // Looking for infinite stacks (ie more links than items allowed) // and for items which have bad coordinates (can't find their stack) for (int obj = igrd(*ri); obj != NON_ITEM; obj = mitm[obj].link) { if (obj < 0 || obj > MAX_ITEMS) { if (igrd(*ri) == obj) { mprf(MSGCH_ERROR, "Igrd has invalid item index %d " "at (%d, %d)", obj, ri->x, ri->y); } else { mprf(MSGCH_ERROR, "Item in stack at (%d, %d) has " "invalid link %d", ri->x, ri->y, obj); } break; } // Check for invalid (zero quantity) items that are linked in. if (!mitm[obj].defined()) { _dump_item(mitm[obj].name(DESC_PLAIN).c_str(), obj, mitm[obj], "Linked invalid item at (%d,%d)!", ri->x, ri->y); } // Check that item knows what stack it's in. if (mitm[obj].pos != *ri) { _dump_item(mitm[obj].name(DESC_PLAIN).c_str(), obj, mitm[obj], "Item position incorrect at (%d,%d)!", ri->x, ri->y); } // If we run into a premarked item we're in real trouble, // this will also keep this from being an infinite loop. if (visited[obj]) { mprf(MSGCH_ERROR, "Potential INFINITE STACK at (%d, %d)", ri->x, ri->y); break; } visited.set(obj); } } // Now scan all the items on the level: for (i = 0; i < MAX_ITEMS; ++i) { if (!mitm[i].defined()) continue; strlcpy(name, mitm[i].name(DESC_PLAIN).c_str(), sizeof(name)); const monster* mon = mitm[i].holding_monster(); // Don't check (-1, -1) player items or (-2, -2) monster items // (except to make sure that the monster is alive). if (mitm[i].pos.origin()) _dump_item(name, i, mitm[i], "Unlinked temporary item:"); else if (mon != NULL && mon->type == MONS_NO_MONSTER) _dump_item(name, i, mitm[i], "Unlinked item held by dead monster:"); else if ((mitm[i].pos.x > 0 || mitm[i].pos.y > 0) && !visited[i]) { _dump_item(name, i, mitm[i], "Unlinked item:"); if (!in_bounds(mitm[i].pos)) { mprf(MSGCH_ERROR, "Item position (%d, %d) is out of bounds", mitm[i].pos.x, mitm[i].pos.y); } else { mprf("igrd(%d,%d) = %d", mitm[i].pos.x, mitm[i].pos.y, igrd(mitm[i].pos)); } // Let's check to see if it's an errant monster object: for (int j = 0; j < MAX_MONSTERS; ++j) for (int k = 0; k < NUM_MONSTER_SLOTS; ++k) { if (menv[j].inv[k] == i) { mprf("Held by monster #%d: %s at (%d,%d)", j, menv[j].name(DESC_A, true).c_str(), menv[j].pos().x, menv[j].pos().y); } } } // Current bad items of interest: // -- armour and weapons with large enchantments/illegal special vals // // -- items described as questionable (the class 100 bug) // // -- eggplant is an illegal throwing weapon // // -- items described as buggy (typically adjectives out of range) // (note: covers buggy, bugginess, buggily, whatever else) // // Theoretically some of these could match random names. // if (strstr(name, "questionable") != NULL || strstr(name, "eggplant") != NULL || strstr(name, "buggy") != NULL || strstr(name, "buggi") != NULL) { _dump_item(name, i, mitm[i], "Bad item:"); } else if ((mitm[i].base_type == OBJ_WEAPONS && (abs(mitm[i].plus) > 30 || abs(mitm[i].plus2) > 30 || !is_artefact(mitm[i]) && mitm[i].special >= NUM_SPECIAL_WEAPONS)) || (mitm[i].base_type == OBJ_ARMOUR && (abs(mitm[i].plus) > 30 || !is_artefact(mitm[i]) && mitm[i].special >= NUM_SPECIAL_ARMOURS))) { _dump_item(name, i, mitm[i], "Bad plus or special value:"); } else if (mitm[i].flags & ISFLAG_SUMMONED && in_bounds(mitm[i].pos)) _dump_item(name, i, mitm[i], "Summoned item on floor:"); } // Quickly scan monsters for "program bug"s. for (i = 0; i < MAX_MONSTERS; ++i) { const monster& mons = menv[i]; if (mons.type == MONS_NO_MONSTER) continue; if (mons.name(DESC_PLAIN, true).find("questionable") != string::npos) { mprf(MSGCH_ERROR, "Program bug detected!"); mprf(MSGCH_ERROR, "Buggy monster detected: monster #%d; position (%d,%d)", i, mons.pos().x, mons.pos().y); } } } #endif #define DEBUG_MONS_SCAN #ifdef DEBUG_MONS_SCAN static void _announce_level_prob(bool warned) { if (!warned && crawl_state.generating_level) { mprf(MSGCH_ERROR, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); mprf(MSGCH_ERROR, "mgrd problem occurred during level generation"); debug_dump_levgen(); } } static bool _inside_vault(const vault_placement& place, const coord_def &pos) { const coord_def delta = pos - place.pos; return delta.x >= 0 && delta.y >= 0 && delta.x < place.size.x && delta.y < place.size.y; } static vector _in_vaults(const coord_def &pos) { vector out; for (unsigned int i = 0; i < env.level_vaults.size(); ++i) { const vault_placement &vault = *env.level_vaults[i]; if (_inside_vault(vault, pos)) out.push_back(vault.map.name); } for (unsigned int i = 0; i < Temp_Vaults.size(); ++i) { const vault_placement &vault = Temp_Vaults[i]; if (_inside_vault(vault, pos)) out.push_back(vault.map.name); } return out; } static string _vault_desc(const coord_def pos) { int mi = env.level_map_ids(pos); if (mi == INVALID_MAP_INDEX) return ""; string out; for (unsigned int i = 0; i < env.level_vaults.size(); ++i) { const vault_placement &vp = *env.level_vaults[i]; if (_inside_vault(vp, pos)) { coord_def br = vp.pos + vp.size - 1; out += make_stringf(" [vault: %s (%d,%d)-(%d,%d) (%dx%d)]", vp.map_name_at(pos).c_str(), vp.pos.x, vp.pos.y, br.x, br.y, vp.size.x, vp.size.y); } } return out; } void debug_mons_scan() { vector bogus_pos; vector bogus_idx; bool warned = false; for (int y = 0; y < GYM; ++y) for (int x = 0; x < GXM; ++x) { const int mons = mgrd[x][y]; if (mons == NON_MONSTER) continue; if (invalid_monster_index(mons)) { mprf(MSGCH_ERROR, "mgrd at (%d, %d) has invalid monster " "index %d", x, y, mons); continue; } const monster* m = &menv[mons]; const coord_def pos(x, y); if (m->pos() != pos) { bogus_pos.push_back(pos); bogus_idx.push_back(mons); _announce_level_prob(warned); mprf(MSGCH_WARN, "Bogosity: mgrd at (%d,%d) points at %s, " "but monster is at (%d,%d)", x, y, m->name(DESC_PLAIN, true).c_str(), m->pos().x, m->pos().y); if (!m->alive()) mprf(MSGCH_WARN, "Additionally, it isn't alive."); warned = true; } else if (!m->alive()) { _announce_level_prob(warned); mprf_nocap(MSGCH_ERROR, "mgrd at (%d,%d) points at dead monster %s", x, y, m->name(DESC_PLAIN, true).c_str()); warned = true; } } ASSERT(you.type == MONS_PLAYER); ASSERT(you.mid == MID_PLAYER); vector floating_mons; bool is_floating[MAX_MONSTERS]; for (int i = 0; i < MAX_MONSTERS; ++i) { is_floating[i] = false; const monster* m = &menv[i]; if (!m->alive()) continue; ASSERT(m->mid > 0); coord_def pos = m->pos(); if (invalid_monster_type(m->type)) { mprf(MSGCH_ERROR, "Bogus monster type %d at (%d, %d), midx = %d", m->type, pos.x, pos.y, i); } if (!in_bounds(pos)) { mprf(MSGCH_ERROR, "Out of bounds monster: %s at (%d, %d), " "midx = %d", m->full_name(DESC_PLAIN, true).c_str(), pos.x, pos.y, i); } else if (mgrd(pos) != i) { floating_mons.push_back(i); is_floating[i] = true; _announce_level_prob(warned); mprf(MSGCH_WARN, "Floating monster: %s at (%d,%d), midx = %d", m->full_name(DESC_PLAIN, true).c_str(), pos.x, pos.y, i); warned = true; for (int j = 0; j < MAX_MONSTERS; ++j) { if (i == j) continue; const monster* m2 = &menv[j]; if (m2->pos() != m->pos()) continue; string full = m2->full_name(DESC_PLAIN, true); if (m2->alive()) { mprf(MSGCH_WARN, "Also at (%d, %d): %s, midx = %d", pos.x, pos.y, full.c_str(), j); } else if (m2->type != MONS_NO_MONSTER) { mprf(MSGCH_WARN, "Dead mon also at (%d, %d): %s," "midx = %d", pos.x, pos.y, full.c_str(), j); } } } // if (mgrd(m->pos()) != i) if (feat_is_wall(grd(pos))) { #if defined(DEBUG_FATAL) // if we're going to dump, point out the culprit env.pgrid(pos) |= FPROP_HIGHLIGHT; #endif mprf(MSGCH_ERROR, "Monster %s in %s at (%d, %d)%s", m->full_name(DESC_PLAIN, true).c_str(), dungeon_feature_name(grd(pos)), pos.x, pos.y, _vault_desc(pos).c_str()); } for (int j = 0; j < NUM_MONSTER_SLOTS; ++j) { const int idx = m->inv[j]; if (idx == NON_ITEM) continue; if (idx < 0 || idx > MAX_ITEMS) { mprf(MSGCH_ERROR, "Monster %s (%d, %d) has invalid item " "index %d in slot %d.", m->full_name(DESC_PLAIN, true).c_str(), pos.x, pos.y, idx, j); continue; } item_def &item(mitm[idx]); if (!item.defined()) { _announce_level_prob(warned); warned = true; mprf(MSGCH_WARN, "Monster %s (%d, %d) holding invalid item in " "slot %d (midx = %d)", m->full_name(DESC_PLAIN, true).c_str(), pos.x, pos.y, j, i); continue; } const monster* holder = item.holding_monster(); if (holder == NULL) { _announce_level_prob(warned); warned = true; _dump_item(item.name(DESC_PLAIN, false, true).c_str(), idx, item, "Monster %s (%d, %d) holding non-monster " "item (midx = %d)", m->full_name(DESC_PLAIN, true).c_str(), pos.x, pos.y, i); continue; } if (holder != m) { _announce_level_prob(warned); warned = true; mprf(MSGCH_WARN, "Monster %s (%d, %d) [midx = %d] holding " "item %s, but item thinks it's held by " "monster %s (%d, %d) [midx = %d]", m->full_name(DESC_PLAIN, true).c_str(), m->pos().x, m->pos().y, i, item.name(DESC_PLAIN).c_str(), holder->full_name(DESC_PLAIN, true).c_str(), holder->pos().x, holder->pos().y, holder->mindex()); bool found = false; for (int k = 0; k < NUM_MONSTER_SLOTS; ++k) { if (holder->inv[k] == idx) { mprf(MSGCH_WARN, "Other monster thinks it's holding the item, too."); found = true; break; } } if (!found) mprf(MSGCH_WARN, "Other monster isn't holding it, though."); } // if (holder != m) } // for (int j = 0; j < NUM_MONSTER_SLOTS; j++) monster* m1 = monster_by_mid(m->mid); if (m1 != m) { if (!m1) die("mid cache bogosity: no monster for %d", m->mid); else if (m1->mid == m->mid) { mprf(MSGCH_ERROR, "Error: monster %s(%d) has same mid as %s(%d) (%d)", m->name(DESC_PLAIN, true).c_str(), m->mindex(), m1->name(DESC_PLAIN, true).c_str(), m1->mindex(), m->mid); } else die("mid cache bogosity: wanted %d got %d", m->mid, m1->mid); } if (you.constricted_by == m->mid && (!m->constricting || m->constricting->find(MID_PLAYER) == m->constricting->end())) { mprf(MSGCH_ERROR, "Error: constricting[you] entry missing for monster %s(%d)", m->name(DESC_PLAIN, true).c_str(), m->mindex()); } if (m->constricted_by) { const actor *h = actor_by_mid(m->constricted_by); if (!h) { mprf(MSGCH_ERROR, "Error: constrictor missing for monster %s(%d)", m->name(DESC_PLAIN, true).c_str(), m->mindex()); } if (!h->constricting || h->constricting->find(m->mid) == h->constricting->end()) { mprf(MSGCH_ERROR, "Error: constricting[%s(mindex=%d mid=%d)] " "entry missing for monster %s(mindex=%d mid=%d)", m->name(DESC_PLAIN, true).c_str(), m->mindex(), m->mid, h->name(DESC_PLAIN, true).c_str(), h->mindex(), h->mid); } } } // for (int i = 0; i < MAX_MONSTERS; ++i) for (map::const_iterator mc = env.mid_cache.begin(); mc != env.mid_cache.end(); ++mc) { unsigned short idx = mc->second; ASSERT(!invalid_monster_index(idx)); if (menv[idx].mid != mc->first) { monster &m(menv[idx]); die("mid cache bogosity: mid %d points to %s mindex=%d mid=%d", mc->first, m.name(DESC_PLAIN, true).c_str(), m.mindex(), m.mid); } } if (in_bounds(you.pos())) if (const monster* m = monster_at(you.pos())) if (!m->submerged() && !fedhas_passthrough(m)) { mprf(MSGCH_ERROR, "Error: player on same spot as monster: %s(%d)", m->name(DESC_PLAIN, true).c_str(), m->mindex()); } // No problems? if (!warned) return; // If this wasn't the result of generating a level then there's nothing // more to report. if (!crawl_state.generating_level) { // Force the dev to notice problems. :P more(); return; } // No vaults to report on? if (env.level_vaults.empty() && Temp_Vaults.empty()) { mprf(MSGCH_ERROR, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); // Force the dev to notice problems. :P more(); return; } mpr(""); for (unsigned int i = 0; i < floating_mons.size(); ++i) { const int idx = floating_mons[i]; const monster* mon = &menv[idx]; vector vaults = _in_vaults(mon->pos()); string str = make_stringf("Floating monster %s (%d, %d)", mon->name(DESC_PLAIN, true).c_str(), mon->pos().x, mon->pos().y); if (vaults.empty()) mprf(MSGCH_WARN, "%s not in any vaults.", str.c_str()); else { mpr_comma_separated_list(str + " in vault(s) ", vaults, " and ", ", ", MSGCH_WARN); } } mpr(""); for (unsigned int i = 0; i < bogus_pos.size(); ++i) { const coord_def pos = bogus_pos[i]; const int idx = bogus_idx[i]; const monster* mon = &menv[idx]; string str = make_stringf("Bogus mgrd (%d, %d) pointing to %s", pos.x, pos.y, mon->name(DESC_PLAIN, true).c_str()); vector vaults = _in_vaults(pos); if (vaults.empty()) mprf(MSGCH_WARN, "%s not in any vaults.", str.c_str()); else { mpr_comma_separated_list(str + " in vault(s) ", vaults, " and ", ", ", MSGCH_WARN); } // Don't report on same monster twice. if (is_floating[idx]) continue; str = "Monster pointed to"; vaults = _in_vaults(mon->pos()); if (vaults.empty()) mprf(MSGCH_WARN, "%s not in any vaults.", str.c_str()); else { mpr_comma_separated_list(str + " in vault(s) ", vaults, " and ", ", ", MSGCH_WARN); } } mprf(MSGCH_ERROR, "!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!"); // Force the dev to notice problems. :P more(); } #endif /** * Check the map for validity, generating a crash if not. * * This checks the loaded map to make sure all dungeon features and shops are * valid, all branch exits are generated, and all portals generated at fixed * levels in the Depths are actually present. */ void check_map_validity() { #ifdef ASSERTS dungeon_feature_type portal = DNGN_UNSEEN; if (you.where_are_you == BRANCH_DEPTHS) { if (you.depth == 3) portal = DNGN_ENTER_PANDEMONIUM; else if (you.depth == 4) portal = DNGN_ENTER_ABYSS; else if (you.depth == 2) portal = DNGN_ENTER_HELL; } dungeon_feature_type exit = DNGN_UNSEEN; if (you.depth == 1 && you.where_are_you != root_branch) exit = branches[you.where_are_you].exit_stairs; // these may require you to look farther: if (exit == DNGN_EXIT_PANDEMONIUM) exit = DNGN_TRANSIT_PANDEMONIUM; if (exit == DNGN_EXIT_ABYSS) exit = DNGN_UNSEEN; for (rectangle_iterator ri(0); ri; ++ri) { dungeon_feature_type feat = grd(*ri); if (feat <= DNGN_UNSEEN || feat >= NUM_FEATURES) die("invalid feature %d at (%d,%d)", feat, ri->x, ri->y); const char *name = dungeon_feature_name(feat); ASSERT(name); ASSERT(*name); // placeholders get empty names find_trap(*ri); // this has all needed asserts already if (shop_struct *shop = get_shop(*ri)) ASSERT_RANGE(shop->type, 0, NUM_SHOPS); // border must be impassable if (!in_bounds(*ri)) ASSERT(feat_is_solid(feat)); if (env.level_map_mask(*ri) & MMT_MIMIC) continue; // no mimics below const dungeon_feature_type orig = orig_terrain(*ri); if (feat == portal || orig == portal) portal = DNGN_UNSEEN; if (feat == exit || orig == exit) exit = DNGN_UNSEEN; } if (portal) { #ifdef DEBUG_DIAGNOSTICS dump_map("missing_portal.map", true); #endif die("Portal %s[%d] didn't get generated.", dungeon_feature_name(portal), portal); } if (exit) { #ifdef DEBUG_DIAGNOSTICS dump_map("missing_exit.map", true); #endif die("Exit %s[%d] didn't get generated.", dungeon_feature_name(exit), exit); } // And just for good measure: debug_item_scan(); debug_mons_scan(); #endif } #ifdef DEBUG_DIAGNOSTICS static FILE *stat_outf; const static char *stat_out_prefix = "objstat_"; const static char *stat_out_ext = ".txt"; #define STAT_PRECISION 2 enum item_base_type { ITEM_FOOD, ITEM_GOLD, ITEM_SCROLLS, ITEM_POTIONS, ITEM_WANDS, ITEM_WEAPONS, ITEM_MISSILES, ITEM_STAVES, ITEM_ARMOUR, ITEM_JEWELLERY, ITEM_MISCELLANY, ITEM_RODS, ITEM_DECKS, ITEM_BOOKS, ITEM_ARTEBOOKS, ITEM_MANUALS, NUM_ITEM_BASE_TYPES, ITEM_IGNORE = 100, }; enum antiquity_level { ANTIQ_ORDINARY, ANTIQ_ARTEFACT, ANTIQ_ALL, }; class item_type { public: item_type(item_base_type ct, int st) : base_type(ct), sub_type(st) { } item_type(item_def &item); item_base_type base_type; int sub_type; }; static level_id all_lev(NUM_BRANCHES, -1); static map > stat_branches; static int num_branches = 0; static int num_levels = 0; // item_recs[level_id][item.base_type][item.sub_type][field] static map >, NUM_ITEM_BASE_TYPES> > item_recs; // weapon_brands[level_id][item.base_type][item.sub_type][antiquity_level][brand]; // arte_sum is 0 for ordinary, 1 for artefact, or 2 for all static map > > > > equip_brands; static map > > > armour_brands; static map > > missile_brands; static FixedVector< vector, NUM_ITEM_BASE_TYPES> item_fields; static const char* equip_brand_fields[] = {"OrdBrandNums", "ArteBrandNums", "AllBrandNums"}; static const char* missile_brand_field = "BrandNums"; static map valid_foods; static vector monster_fields; static map valid_monsters; static map > > monster_recs; static bool _is_valid_food_type(int sub_type) { return sub_type != FOOD_CHUNK && food_type_name(sub_type) != "buggy"; } static void _init_foods() { int num_foods = 0; for (int i = 0; i < NUM_FOODS; i++) { if (_is_valid_food_type(i)) valid_foods[i] = num_foods++; } } static void _init_monsters() { int num_mons = 0; for (int i = 0; i < NUM_MONSTERS; i++) { monster_type mc = static_cast(i); if (!mons_class_flag(mc, M_NO_EXP_GAIN) && !mons_class_flag(mc, M_CANT_SPAWN)) { valid_monsters[mc] = num_mons++; } } // For the all-monster summary valid_monsters[NUM_MONSTERS] = num_mons; } static item_base_type _item_base_type(item_def &item) { item_base_type type; switch (item.base_type) { case OBJ_MISCELLANY: if (item.sub_type >= MISC_FIRST_DECK && item.sub_type <= MISC_LAST_DECK) type = ITEM_DECKS; else type = ITEM_MISCELLANY; break; case OBJ_BOOKS: if (item.sub_type == BOOK_MANUAL) type = ITEM_MANUALS; else if (is_artefact(item)) type = ITEM_ARTEBOOKS; else type = ITEM_BOOKS; break; case OBJ_FOOD: if (valid_foods.count(item.sub_type)) type = ITEM_FOOD; else type = ITEM_IGNORE; break; case OBJ_GOLD: type = ITEM_GOLD; break; case OBJ_SCROLLS: type = ITEM_SCROLLS; break; case OBJ_POTIONS: type = ITEM_POTIONS; break; case OBJ_WANDS: type = ITEM_WANDS; break; case OBJ_WEAPONS: type = ITEM_WEAPONS; break; case OBJ_MISSILES: type = ITEM_MISSILES; break; case OBJ_STAVES: type = ITEM_STAVES; break; case OBJ_ARMOUR: type = ITEM_ARMOUR; break; case OBJ_JEWELLERY: type = ITEM_JEWELLERY; break; case OBJ_RODS: type = ITEM_RODS; break; default: type = ITEM_IGNORE; break; } return type; } static object_class_type _item_orig_base_type(item_base_type base_type) { object_class_type type; switch (base_type) { case ITEM_FOOD: type = OBJ_FOOD; break; case ITEM_GOLD: type = OBJ_GOLD; break; case ITEM_SCROLLS: type = OBJ_SCROLLS; break; case ITEM_POTIONS: type = OBJ_POTIONS; break; case ITEM_WANDS: type = OBJ_WANDS; break; case ITEM_WEAPONS: type = OBJ_WEAPONS; break; case ITEM_MISSILES: type = OBJ_MISSILES; break; case ITEM_STAVES: type = OBJ_STAVES; break; case ITEM_ARMOUR: type = OBJ_ARMOUR; break; case ITEM_JEWELLERY: type = OBJ_JEWELLERY; break; case ITEM_RODS: type = OBJ_RODS; break; case ITEM_DECKS: case ITEM_MISCELLANY: type = OBJ_MISCELLANY; break; case ITEM_ARTEBOOKS: case ITEM_MANUALS: case ITEM_BOOKS: type = OBJ_BOOKS; break; default: type = OBJ_UNASSIGNED; break; } return type; } // Get the actual food subtype static int _orig_food_subtype(int sub_type) { map::const_iterator mi; for (mi = valid_foods.begin(); mi != valid_foods.end(); mi++) { if (mi->second == sub_type) return mi->first; } die("Invalid food subtype"); return 0; } static string _item_class_name(item_base_type base_type) { string name; switch (base_type) { case ITEM_DECKS: name = "Decks"; break; case ITEM_ARTEBOOKS: name = "Artefact Spellbooks"; break; case ITEM_MANUALS: name = "Manuals"; break; default: name = item_class_name(_item_orig_base_type(base_type)); } return name; } static int _item_orig_sub_type(item_type &item) { int type; switch (item.base_type) { case ITEM_DECKS: type = item.sub_type + MISC_FIRST_DECK; break; case ITEM_MISCELLANY: if (item.sub_type >= MISC_FIRST_DECK) type = item.sub_type + MISC_LAST_DECK - MISC_FIRST_DECK + 1; else type = item.sub_type; break; case ITEM_FOOD: type = _orig_food_subtype(item.sub_type); break; case ITEM_ARTEBOOKS: type = item.sub_type + BOOK_RANDART_LEVEL; break; case ITEM_MANUALS: type = BOOK_MANUAL; break; default: type = item.sub_type; break; } return type; } static int _item_max_sub_type(item_base_type base_type) { int num = 0; switch (base_type) { case ITEM_FOOD: num = valid_foods.size(); break; case ITEM_MISCELLANY: // Decks not counted here. num = NUM_MISCELLANY - (MISC_LAST_DECK - MISC_FIRST_DECK + 1); break; case ITEM_DECKS: num = MISC_LAST_DECK - MISC_FIRST_DECK + 1; break; case ITEM_BOOKS: num = MAX_RARE_BOOK + 1; break; case ITEM_ARTEBOOKS: num = 2; break; case ITEM_MANUALS: num = 1; break; default: num = get_max_subtype(_item_orig_base_type(base_type)); break; } return num; } static item_def _dummy_item(item_type &item) { item_def dummy_item; dummy_item.base_type = _item_orig_base_type(item.base_type); dummy_item.sub_type = _item_orig_sub_type(item); // Deck name is reported as buggy if this is not done. if (item.base_type == ITEM_DECKS) { dummy_item.plus = 1; dummy_item.special = DECK_RARITY_COMMON; init_deck(dummy_item); } return dummy_item; } static bool _item_has_antiquity(item_base_type base_type) { switch (base_type) { case ITEM_WEAPONS: case ITEM_ARMOUR: case ITEM_JEWELLERY: return true; default: return false; } } item_type::item_type(item_def &item) { base_type = _item_base_type(item); if (base_type == ITEM_DECKS) sub_type = item.sub_type - MISC_FIRST_DECK; else if (base_type == ITEM_MISCELLANY && item.sub_type > MISC_LAST_DECK) sub_type = item.sub_type - (MISC_LAST_DECK - MISC_FIRST_DECK + 1); else if (base_type == ITEM_FOOD) sub_type = valid_foods[item.sub_type]; else if (base_type == ITEM_ARTEBOOKS) sub_type = item.sub_type - BOOK_RANDART_LEVEL; else if (base_type == ITEM_MANUALS) sub_type = 0; else sub_type = item.sub_type; } // This could be done without the macros in C++11, where std::vector has a // constructor that takes an initializer list. #define INIT_VEC(vec, ...) do \ { \ vector &x = (vec); \ static const char *const ary[] = { __VA_ARGS__ }; \ x.insert(x.begin(), ary, ary + ARRAYSZ(ary)); \ } while (0) #define ITEM_FIELDS(name, ...) INIT_VEC(item_fields[ITEM_ ## name], __VA_ARGS__) static void _init_fields() { ITEM_FIELDS(SCROLLS, "Num", "NumMin", "NumMax", "NumSD", "NumHeldMons", "NumPiles", "PileQuant"); ITEM_FIELDS(POTIONS, "Num", "NumMin", "NumMax", "NumSD", "NumHeldMons", "NumPiles", "PileQuant"); ITEM_FIELDS(FOOD, "Num", "NumMin", "NumMax", "NumSD", "NumPiles", "PileQuant", "TotalNormNutr", "TotalCarnNutr", "TotalHerbNutr"); ITEM_FIELDS(GOLD, "Num", "NumMin", "NumMax", "NumSD", "NumHeldMons", "NumPiles", "PileQuant"); ITEM_FIELDS(WANDS, "Num", "NumMin", "NumMax", "NumSD", "NumHeldMons", "WandCharges"); ITEM_FIELDS(WEAPONS, "OrdNum", "ArteNum", "AllNum", "AllNumMin", "AllNumMax", "AllNumSD", "OrdEnch", "ArteEnch", "AllEnch", "OrdNumCursed", "ArteNumCursed", "AllNumCursed", "OrdNumBranded", "OrdNumHeldMons", "ArteNumHeldMons", "AllNumHeldMons"); ITEM_FIELDS(STAVES, "Num", "NumMin", "NumMax", "NumSD", "NumCursed", "NumHeldMons"); ITEM_FIELDS(ARMOUR, "OrdNum", "ArteNum", "AllNum", "AllNumMin", "AllNumMax", "AllNumSD", "OrdEnch", "ArteEnch", "AllEnch", "OrdNumCursed", "ArteNumCursed", "AllNumCursed", "OrdNumBranded", "OrdNumHeldMons", "ArteNumHeldMons", "AllNumHeldMons"); ITEM_FIELDS(JEWELLERY, "OrdNum", "ArteNum", "AllNum", "AllNumMin", "AllNumMax", "AllNumSD", "OrdNumCursed", "ArteNumCursed", "AllNumCursed", "OrdNumHeldMons", "ArteNumHeldMons", "AllNumHeldMons", "OrdEnch", "ArteEnch", "AllEnch"); ITEM_FIELDS(RODS, "Num", "NumMin", "NumMax", "NumSD", "NumHeldMons", "RodMana", "RodRecharge", "NumCursed"); ITEM_FIELDS(MISSILES, "Num", "NumMin", "NumMax", "NumSD", "NumHeldMons", "NumBranded", "NumPiles", "PileQuant"); ITEM_FIELDS(MISCELLANY, "Num", "NumMin", "NumMax", "NumSD", "MiscPlus"); ITEM_FIELDS(DECKS, "PlainNum", "OrnateNum", "LegendaryNum", "AllNum", "AllNumMin", "AllNumMax", "AllNumSD", "AllDeckCards"); ITEM_FIELDS(BOOKS, "Num", "NumMin", "NumMax", "NumSD"); ITEM_FIELDS(ARTEBOOKS, "Num", "NumMin", "NumMax", "NumSD"); ITEM_FIELDS(MANUALS, "Num", "NumMin", "NumMax", "NumSD"); INIT_VEC(monster_fields, "Num", "NumMin", "NumMax", "NumSD", "MonsHD", "MonsHP", "MonsXP", "TotalXP", "MonsNumChunks", "TotalNutr"); } static void _init_stats() { map >::const_iterator bi; for (bi = stat_branches.begin(); bi != stat_branches.end(); bi++) { for (unsigned int l = 0; l <= bi->second.size(); l++) { level_id lev; if (l == bi->second.size()) { if (bi->first == NUM_BRANCHES) continue; lev.branch = bi->first; lev.depth = -1; } else lev = (bi->second)[l]; for (int i = 0; i < NUM_ITEM_BASE_TYPES; i++) { item_base_type base_type = static_cast(i); string min_field = _item_has_antiquity(base_type) ? "AllNumMin" : "NumMin"; string max_field = _item_has_antiquity(base_type) ? "AllNumMax" : "NumMax"; for (int j = 0; j <= _item_max_sub_type(base_type); j++) { for (unsigned int k = 0; k < item_fields[i].size(); k++) item_recs[lev][i][j][item_fields[i][k]] = 0; // For determining the NumSD, NumMin and NumMax fields. item_recs[lev][i][j]["NumForIter"] = 0; item_recs[lev][i][j][min_field] = INFINITY; item_recs[lev][i][j][max_field] = -1; } } equip_brands[lev] = vector< vector< vector< vector > > >(); equip_brands[lev].push_back(vector< vector< vector > >()); for (int i = 0; i <= NUM_WEAPONS; i++) { equip_brands[lev][0].push_back(vector< vector >()); for (int j = 0; j < 3; j++) { equip_brands[lev][0][i].push_back(vector()); for (int k = 0; k < NUM_SPECIAL_WEAPONS; k++) equip_brands[lev][0][i][j].push_back(0); } } equip_brands[lev].push_back(vector< vector< vector > >()); for (int i = 0; i <= NUM_ARMOURS; i++) { equip_brands[lev][1].push_back(vector< vector >()); for (int j = 0; j < 3; j++) { equip_brands[lev][1][i].push_back(vector()); for (int k = 0; k < NUM_SPECIAL_ARMOURS; k++) equip_brands[lev][1][i][j].push_back(0); } } missile_brands[lev] = vector< vector >(); for (int i = 0; i <= NUM_MISSILES; i++) { missile_brands[lev].push_back(vector()); for (int j = 0; j < NUM_SPECIAL_ARMOURS; j++) missile_brands[lev][i].push_back(0); } map::const_iterator mi; for (mi = valid_monsters.begin(); mi != valid_monsters.end(); mi++) { for (unsigned int i = 0; i < monster_fields.size(); i++) monster_recs[lev][mi->second][monster_fields[i]] = 0; // For determining the NumMin and NumMax fields. monster_recs[lev][mi->second]["NumForIter"] = 0; monster_recs[lev][mi->second]["NumMin"] = INFINITY; monster_recs[lev][mi->second]["NumMax"] = -1; } } } } static void _record_item_stat(level_id &lev, item_type &item, string field, double value) { int class_sum = _item_max_sub_type(item.base_type); level_id br_lev(lev.branch, -1); item_recs[lev][item.base_type][item.sub_type][field] += value; item_recs[lev][item.base_type][class_sum][field] += value; item_recs[br_lev][item.base_type][item.sub_type][field] += value; item_recs[br_lev][item.base_type][class_sum][field] += value; item_recs[all_lev][item.base_type][item.sub_type][field] += value; item_recs[all_lev][item.base_type][class_sum][field] += value; } static void _record_brand(level_id &lev, item_type &item, int quantity, bool is_arte, int brand) { ASSERT(item.base_type == ITEM_WEAPONS || item.base_type == ITEM_ARMOUR || item.base_type == ITEM_MISSILES); int cst = _item_max_sub_type(item.base_type); int antiq = is_arte ? ANTIQ_ARTEFACT : ANTIQ_ORDINARY; bool is_equip = item.base_type == ITEM_WEAPONS || item.base_type == ITEM_ARMOUR; int bt = item.base_type == ITEM_WEAPONS ? 0 : 1; int st = item.sub_type; level_id br_lev(lev.branch, -1); if (is_equip) { equip_brands[lev][bt][st][antiq][brand] += quantity; equip_brands[lev][bt][st][ANTIQ_ALL][brand] += quantity; equip_brands[lev][bt][cst][antiq][brand] += quantity; equip_brands[lev][bt][cst][ANTIQ_ALL][brand] += quantity; equip_brands[br_lev][bt][st][antiq][brand] += quantity; equip_brands[br_lev][bt][st][ANTIQ_ALL][brand] += quantity; equip_brands[br_lev][bt][cst][antiq][brand] += quantity; equip_brands[br_lev][bt][cst][ANTIQ_ALL][brand] += quantity; equip_brands[all_lev][bt][st][antiq][brand] += quantity; equip_brands[all_lev][bt][st][ANTIQ_ALL][brand] += quantity; equip_brands[all_lev][bt][cst][antiq][brand] += quantity; equip_brands[all_lev][bt][cst][ANTIQ_ALL][brand] += quantity; } else { missile_brands[lev][st][brand] += quantity; missile_brands[lev][cst][brand] += quantity; missile_brands[br_lev][st][brand] += quantity; missile_brands[br_lev][cst][brand] += quantity; missile_brands[all_lev][st][brand] += quantity; missile_brands[all_lev][cst][brand] += quantity; } } static bool _item_track_piles(item_base_type base_type) { switch (base_type) { case ITEM_GOLD: case ITEM_POTIONS: case ITEM_SCROLLS: case ITEM_FOOD: case ITEM_MISSILES: return true; default: return false; } } static bool _item_track_curse(item_base_type base_type) { switch (base_type) { case ITEM_WEAPONS: case ITEM_STAVES: case ITEM_ARMOUR: case ITEM_JEWELLERY: case ITEM_RODS: return true; default: return false; } } static bool _item_track_plus(item_base_type base_type) { switch (base_type) { case ITEM_WEAPONS: case ITEM_ARMOUR: case ITEM_JEWELLERY: case ITEM_WANDS: case ITEM_MISCELLANY: case ITEM_DECKS: return true; default: return false; } } static bool _item_track_brand(item_base_type base_type) { switch (base_type) { case ITEM_WEAPONS: case ITEM_ARMOUR: case ITEM_MISSILES: return true; default: return false; } } static bool _item_track_monster(item_base_type base_type) { switch (base_type) { case ITEM_GOLD: case ITEM_POTIONS: case ITEM_SCROLLS: case ITEM_WANDS: case ITEM_WEAPONS: case ITEM_STAVES: case ITEM_ARMOUR: case ITEM_RODS: case ITEM_MISSILES: case ITEM_JEWELLERY: return true; default: return false; } } void objstat_record_item(item_def &item) { level_id cur_lev = level_id::current(); item_type itype(item); bool is_arte = is_artefact(item); int brand = -1; bool has_antiq = _item_has_antiquity(itype.base_type); string all_num_f = has_antiq ? "AllNum" : "Num"; string antiq_num_f = is_arte ? "ArteNum" : "OrdNum"; string all_cursed_f = has_antiq ? "AllNumCursed" : "NumCursed"; string antiq_cursed_f = is_arte ? "ArteNumCursed" : "OrdNumCursed"; string all_num_hm_f = has_antiq ? "AllNumHeldMons" : "NumHeldMons"; string antiq_num_hm_f = is_arte ? "ArteNumHeldMons" : "OrdNumHeldMons"; string all_plus_f = "AllEnch"; string antiq_plus_f = is_arte ? "ArteEnch" : "OrdEnch"; string num_brand_f = has_antiq ? "OrdNumBranded" : "NumBranded"; // Just in case, don't count mimics as items; these are converted // explicitely in mg_do_build_level(). if (item.flags & ISFLAG_MIMIC || itype.base_type == ITEM_IGNORE) return; // Some type-specific calculations. switch (itype.base_type) { case ITEM_MISSILES: brand = get_ammo_brand(item); break; case ITEM_FOOD: _record_item_stat(cur_lev, itype, "TotalNormNutr", food_value(item)); // Set these dietary mutations so we can get accurate nutrition. you.mutation[MUT_CARNIVOROUS] = 3; _record_item_stat(cur_lev, itype, "TotalCarnNutr", food_value(item) * food_is_meaty(item.sub_type)); you.mutation[MUT_CARNIVOROUS] = 0; you.mutation[MUT_HERBIVOROUS] = 3; _record_item_stat(cur_lev, itype, "TotalHerbNutr", food_value(item) * food_is_veggie(item.sub_type)); you.mutation[MUT_HERBIVOROUS] = 0; break; case ITEM_WEAPONS: brand = get_weapon_brand(item); break; case ITEM_ARMOUR: brand = get_armour_ego_type(item); break; case ITEM_WANDS: all_plus_f = "WandCharges"; break; case ITEM_RODS: _record_item_stat(cur_lev, itype, "RodMana", item.plus2 / ROD_CHARGE_MULT); _record_item_stat(cur_lev, itype, "RodRecharge", item.special); break; case ITEM_MISCELLANY: all_plus_f = "MiscPlus"; break; case ITEM_DECKS: switch (deck_rarity(item)) { case DECK_RARITY_COMMON: _record_item_stat(cur_lev, itype, "PlainNum", 1); break; case DECK_RARITY_RARE: _record_item_stat(cur_lev, itype, "OrnateNum", 1); break; case DECK_RARITY_LEGENDARY: _record_item_stat(cur_lev, itype, "LegendaryNum", 1); break; default: break; } all_plus_f = "AllDeckCards"; break; default: break; } if (_item_track_piles(itype.base_type)) _record_item_stat(cur_lev, itype, "NumPiles", 1); if (_item_track_curse(itype.base_type) && item.cursed()) { if (has_antiq) _record_item_stat(cur_lev, itype, antiq_cursed_f, 1); _record_item_stat(cur_lev, itype, all_cursed_f, 1); } if (_item_track_plus(itype.base_type)) { if (has_antiq) _record_item_stat(cur_lev, itype, antiq_plus_f, item.plus); _record_item_stat(cur_lev, itype, all_plus_f, item.plus); } if (_item_track_brand(itype.base_type)) { _record_brand(cur_lev, itype, item.quantity, is_arte, brand); if (!is_arte && brand > 0) _record_item_stat(cur_lev, itype, num_brand_f, item.quantity); } if (_item_track_monster(itype.base_type) && item.holding_monster()) { if (has_antiq) _record_item_stat(cur_lev, itype, antiq_num_hm_f, item.quantity); _record_item_stat(cur_lev, itype, all_num_hm_f, item.quantity); } if (has_antiq) _record_item_stat(cur_lev, itype, antiq_num_f, item.quantity); _record_item_stat(cur_lev, itype, all_num_f, item.quantity); _record_item_stat(cur_lev, itype, "NumForIter", item.quantity); } static void _record_monster_stat(level_id &lev, int mons_ind, string field, double value) { level_id br_lev(lev.branch, -1); int sum_ind = valid_monsters[NUM_MONSTERS]; monster_recs[lev][mons_ind][field] += value; monster_recs[lev][sum_ind][field] += value; monster_recs[br_lev][mons_ind][field] += value; monster_recs[br_lev][sum_ind][field] += value; monster_recs[all_lev][mons_ind][field] += value; monster_recs[all_lev][sum_ind][field] += value; } void objstat_record_monster(monster *mons) { if (!valid_monsters.count(mons->type)) return; int mons_ind = valid_monsters[mons->type]; corpse_effect_type chunk_effect = mons_corpse_effect(mons->type); bool is_clean = chunk_effect == CE_CLEAN || chunk_effect == CE_POISONOUS; level_id lev = level_id::current(); _record_monster_stat(lev, mons_ind, "Num", 1); _record_monster_stat(lev, mons_ind, "NumForIter", 1); _record_monster_stat(lev, mons_ind, "MonsXP", exper_value(mons)); _record_monster_stat(lev, mons_ind, "TotalXP", exper_value(mons)); _record_monster_stat(lev, mons_ind, "MonsHP", mons->max_hit_points); _record_monster_stat(lev, mons_ind, "MonsHD", mons->get_experience_level()); // Record chunks/nutrition if monster leaves a corpse. if (chunk_effect != CE_NOCORPSE && mons_weight(mons->type)) { // copied from turn_corpse_into_chunks() double chunks = (1 + stepdown_value(get_max_corpse_chunks(mons->type), 4, 4, 12, 12)) / 2.0; _record_monster_stat(lev, mons_ind, "MonsNumChunks", chunks); if (is_clean) { _record_monster_stat(lev, mons_ind, "TotalNutr", chunks * CHUNK_BASE_NUTRITION); } } } void objstat_iteration_stats() { map >::const_iterator bi; for (bi = stat_branches.begin(); bi != stat_branches.end(); bi++) { for (unsigned int l = 0; l <= bi->second.size(); l++) { level_id lev; if (l == bi->second.size()) { if (bi->first == NUM_BRANCHES) continue; lev.branch = bi->first; lev.depth = -1; } else lev = (bi->second)[l]; for (int i = 0; i < NUM_ITEM_BASE_TYPES; i++) { item_base_type base_type = static_cast(i); for (int j = 0; j <= _item_max_sub_type(base_type); j++) { bool use_all = _item_has_antiquity(base_type) || base_type == ITEM_DECKS; string min_f = use_all ? "AllNumMin" : "NumMin"; string max_f = use_all ? "AllNumMax" : "NumMax"; string sd_f = use_all ? "AllNumSD" : "NumSD"; map &stats = item_recs[lev][i][j]; if (stats["NumForIter"] > stats[max_f]) stats[max_f] = stats["NumForIter"]; if (stats["NumForIter"] < stats[min_f]) stats[min_f] = stats["NumForIter"]; stats[sd_f] += stats["NumForIter"] * stats["NumForIter"]; stats["NumForIter"] = 0; } } map::const_iterator mi; for (mi = valid_monsters.begin(); mi != valid_monsters.end(); mi++) { map &stats = monster_recs[lev][mi->second]; if (stats["NumForIter"] > stats["NumMax"]) stats["NumMax"] = stats["NumForIter"]; if (stats["NumForIter"] < stats["NumMin"]) stats["NumMin"] = stats["NumForIter"]; stats["NumSD"] += stats["NumForIter"] * stats["NumForIter"]; stats["NumForIter"] = 0; } } } } static void _write_level_headers(branch_type br, int num_fields) { unsigned int level_count = 0; vector &levels = stat_branches[br]; vector::const_iterator li; fprintf(stat_outf, "Place"); for (li = levels.begin(); li != levels.end(); li++) { if (br == NUM_BRANCHES) fprintf(stat_outf, "\tAllLevels"); else fprintf(stat_outf, "\t%s", li->describe().c_str()); for (int i = 0; i < num_fields - 1; i++) fprintf(stat_outf, "\t"); if (++level_count == levels.size() && level_count > 1) { fprintf(stat_outf, "\t%s", branches[li->branch].abbrevname); for (int i = 0; i < num_fields - 1; i++) fprintf(stat_outf, "\t"); } } fprintf(stat_outf, "\n"); } static void _write_stat_headers(branch_type br, vector fields) { unsigned int level_count = 0; vector &levels = stat_branches[br]; vector::const_iterator li; fprintf(stat_outf, "Property"); for (li = levels.begin(); li != levels.end(); li++) { for (unsigned int i = 0; i < fields.size(); i++) fprintf(stat_outf, "\t%s", fields[i].c_str()); if (++level_count == levels.size() && level_count > 1) { for (unsigned int i = 0; i < fields.size(); i++) fprintf(stat_outf, "\t%s", fields[i].c_str()); } } fprintf(stat_outf, "\n"); } static void _write_stat(map &stats, string field) { ostringstream output; double value = 0; output.precision(STAT_PRECISION); output.setf(ios_base::fixed); if (field == "PileQuant") value = stats["Num"] / stats["NumPiles"]; else if (field == "WandCharges" || field == "RodMana" || field == "RodRecharge" || field == "MiscPlus" || field == "MonsHD" || field == "MonsHP" || field == "MonsXP" || field == "MonsNumChunks") { value = stats[field] / stats["Num"]; } else if (field == "AllEnch" || field == "AllDeckCards") value = stats[field] / stats["AllNum"]; else if (field == "ArteEnch") value = stats[field] / stats["ArteNum"]; else if (field == "OrdEnch") value = stats[field] / stats["OrdNum"]; else if (field == "NumSD" || field == "AllNumSD") { string num_f = field == "NumSD" ? "Num" : "AllNum"; if (SysEnv.map_gen_iters == 1) value = 0; else { double mean = stats[num_f] / SysEnv.map_gen_iters; value = sqrt((SysEnv.map_gen_iters / (SysEnv.map_gen_iters - 1.0)) * (stats[field] / SysEnv.map_gen_iters - mean * mean)); } } else if (field == "NumMin" || field == "AllNumMin" || field == "NumMax" || field == "AllNumMax") { value = stats[field]; } else value = stats[field] / SysEnv.map_gen_iters; output << "\t" << value; fprintf(stat_outf, "%s", output.str().c_str()); } static string _brand_name(item_type &item, int brand) { string brand_name = ""; item_def dummy_item = _dummy_item(item); if (!brand) brand_name = "none"; else { dummy_item.special = brand; switch (item.base_type) { case ITEM_WEAPONS: brand_name = weapon_brand_name(dummy_item, true); break; case ITEM_ARMOUR: brand_name = armour_ego_name(dummy_item, true); break; case ITEM_MISSILES: brand_name = missile_brand_name(dummy_item, MBN_TERSE); break; default: break; } } return brand_name; } static void _write_brand_stats(vector &brand_stats, item_type &item) { ASSERT(item.base_type == ITEM_WEAPONS || item.base_type == ITEM_ARMOUR || item.base_type == ITEM_MISSILES); item_def dummy_item = _dummy_item(item); unsigned int num_brands = brand_stats.size(); bool first_brand = true; ostringstream brand_summary; brand_summary.setf(ios_base::fixed); brand_summary.precision(STAT_PRECISION); for (unsigned int i = 0; i < num_brands; i++) { string brand_name = ""; double value = (double) brand_stats[i] / SysEnv.map_gen_iters; if (brand_stats[i] == 0) continue; if (first_brand) first_brand = false; else brand_summary << ","; brand_name = _brand_name(item, i); brand_summary << brand_name.c_str() << "(" << value << ")"; } fprintf(stat_outf, "\t%s", brand_summary.str().c_str()); } static string _item_name(item_type &item) { string name = ""; if (item.sub_type == _item_max_sub_type(item.base_type)) name = "All " + _item_class_name(item.base_type); else if (item.base_type == ITEM_MANUALS) name = "Manual"; else if (item.base_type == ITEM_ARTEBOOKS) { int orig_type = _item_orig_sub_type(item); if (orig_type == BOOK_RANDART_LEVEL) name = "Level Artefact Book"; else name = "Theme Artefact Book"; } else { item_def dummy_item = _dummy_item(item); name = dummy_item.name(DESC_DBNAME, true, true); } return name; } static void _write_item_stats(branch_type br, item_type &item) { bool is_brand_equip = item.base_type == ITEM_WEAPONS || item.base_type == ITEM_ARMOUR; int equip_ind = is_brand_equip ? (item.base_type == ITEM_WEAPONS ? 0 : 1) : -1; unsigned int level_count = 0; vector fields = item_fields[item.base_type]; vector::const_iterator li; fprintf(stat_outf, "%s", _item_name(item).c_str()); for (li = stat_branches[br].begin(); li != stat_branches[br].end(); li++) { map &item_stats = item_recs[*li][item.base_type][item.sub_type]; for (unsigned int i = 0; i < fields.size(); i++) _write_stat(item_stats, fields[i]); if (is_brand_equip) { vector< vector > &brand_stats = equip_brands[*li][equip_ind][item.sub_type]; for (int j = 0; j < 3; j++) _write_brand_stats(brand_stats[j], item); } else if (item.base_type == ITEM_MISSILES) _write_brand_stats(missile_brands[*li][item.sub_type], item); if (++level_count == stat_branches[li->branch].size() && level_count > 1) { level_id br_lev(li->branch, -1); map &branch_stats = item_recs[br_lev][item.base_type][item.sub_type]; for (unsigned int i = 0; i < fields.size(); i++) _write_stat(branch_stats, fields[i]); if (is_brand_equip) { vector< vector > &branch_brand_stats = equip_brands[br_lev][equip_ind][item.sub_type]; for (int j = 0; j < 3; j++) _write_brand_stats(branch_brand_stats[j], item); } else if (item.base_type == ITEM_MISSILES) { _write_brand_stats(missile_brands[br_lev][item.sub_type], item); } } } fprintf(stat_outf, "\n"); } static void _write_monster_stats(branch_type br, monster_type mons_type, int mons_ind) { unsigned int level_count = 0; vector fields = monster_fields; vector::const_iterator li; if (mons_ind == valid_monsters[NUM_MONSTERS]) fprintf(stat_outf, "All Monsters"); else fprintf(stat_outf, "%s", mons_type_name(mons_type, DESC_PLAIN).c_str()); for (li = stat_branches[br].begin(); li != stat_branches[br].end(); li++) { for (unsigned int i = 0; i < fields.size(); i++) _write_stat(monster_recs[*li][mons_ind], fields[i]); if (++level_count == stat_branches[li->branch].size() && level_count > 1) { level_id br_lev(li->branch, -1); for (unsigned int i = 0; i < fields.size(); i++) _write_stat(monster_recs[br_lev][mons_ind], fields[i]); } } fprintf(stat_outf, "\n"); } static void _write_branch_stats(branch_type br) { fprintf(stat_outf, "\n\nItem Generation Stats:"); for (int i = 0; i < NUM_ITEM_BASE_TYPES; i++) { item_base_type base_type = static_cast(i); int num_types = _item_max_sub_type(base_type); vector fields = item_fields[base_type]; fprintf(stat_outf, "\n%s\n", _item_class_name(base_type).c_str()); if (base_type == ITEM_WEAPONS || base_type == ITEM_ARMOUR) { for (int j = 0; j < 3; j++) fields.push_back(equip_brand_fields[j]); } else if (base_type == ITEM_MISSILES) fields.push_back(missile_brand_field); _write_level_headers(br, fields.size()); _write_stat_headers(br, fields); for (int j = 0; j <= num_types; j++) { item_type item(base_type, j); _write_item_stats(br, item); } } fprintf(stat_outf, "\n\nMonster Generation Stats:\n"); _write_level_headers(br, monster_fields.size()); _write_stat_headers(br, monster_fields); map::const_iterator mi; for (mi = valid_monsters.begin(); mi != valid_monsters.end(); mi++) _write_monster_stats(br, mi->first, mi->second); } static void _write_object_stats() { string all_desc = ""; map >::const_iterator bi; for (bi = stat_branches.begin(); bi != stat_branches.end(); bi++) { string branch_name; if (bi->first == NUM_BRANCHES) { if (num_branches == 1) continue; branch_name = "AllLevels"; if (SysEnv.map_gen_range.get()) all_desc = SysEnv.map_gen_range.get()->describe(); else all_desc = "All Branches"; all_desc = "Levels included in AllLevels: " + all_desc + "\n"; } else branch_name = branches[bi->first].abbrevname; ostringstream out_file; out_file << stat_out_prefix << branch_name << stat_out_ext; stat_outf = fopen(out_file.str().c_str(), "w"); if (!stat_outf) { fprintf(stderr, "Unable to open objstat output file: %s\n" "Error: %s", out_file.str().c_str(), strerror(errno)); end(1); } fprintf(stat_outf, "Object Generation Stats\n" "Number of iterations: %d\n" "Number of branches: %d\n" "%s" "Number of levels: %d\n" "Version: %s\n", SysEnv.map_gen_iters, num_branches, all_desc.c_str(), num_levels, Version::Long); _write_branch_stats(bi->first); fclose(stat_outf); fprintf(stdout, "Wrote statistics for branch %s to %s.\n", branch_name.c_str(), out_file.str().c_str()); } } void objstat_generate_stats() { // Warn assertions about possible oddities like the artefact list being // cleared. you.wizard = true; // Let "acquire foo" have skill aptitudes to work with. you.species = SP_HUMAN; initialise_item_descriptions(); initialise_branch_depths(); // We have to run map preludes ourselves. run_map_global_preludes(); run_map_local_preludes(); // Populate a vector of the levels ids we've made for (branch_iterator it; it; ++it) { if (brdepth[it->id] == -1) continue; const branch_type br = it->id; #if TAG_MAJOR_VERSION == 34 // Don't want to include Forest since it doesn't generate if (br == BRANCH_FOREST) continue; #endif vector levels; for (int dep = 1; dep <= brdepth[br]; ++dep) { const level_id lid(br, dep); if (SysEnv.map_gen_range.get() && !SysEnv.map_gen_range->is_usable_in(lid)) { continue; } levels.push_back(lid); ++num_levels; } if (levels.size()) { stat_branches[br] = levels; ++num_branches; } } // This represents the AllLevels summary. stat_branches[NUM_BRANCHES] = vector(); stat_branches[NUM_BRANCHES].push_back(level_id(NUM_BRANCHES, -1)); fprintf(stdout, "Generating object statistics for %d iteration(s) of %d " "level(s) over %d branch(es).\n", SysEnv.map_gen_iters, num_levels, num_branches); _init_fields(); _init_foods(); _init_monsters(); _init_stats(); if (mapstat_build_levels()) _write_object_stats(); } #endif // DEBUG_DIAGNOSTICS