summaryrefslogblamecommitdiffstats
path: root/crawl-ref/source/wiz-item.cc
blob: 451456b65001e48be61caf964d4cd6fe6201cbce (plain) (tree)


















                                              
                
                     




                     
                     
                      

                     
                   
                     
                    
                     
                     


                    












                                                                      


                              


















































































































                                                                                  
                                             
























































                                                                               




                                                                               

















































                                                                     
        


































                                          
                                  









                                                                          

                 














































































































































































































































































































                                                                              
                                                                        

































































                                                                                
                          









































                                                                 
                                                          
     
                                                   
         

                                        
 
                                               
 

                                 
 

                                                       
































































                                                                           









                                                                
                                                               











                                                                         
                                           
                                           
                                           




































                                                                             
                                                                   
















                                                                 
         
                       
                                  
             

                                                        
                                                       






                                                              

             

                                                                             






                                                                   





















                                                      
                                   
                                           










                                                    



                                                   
                                                     
                                                          
                                 
                                                                    
                                             







                                                                   
                      














                                                                       
                          
         
     

                                                              

                                                  
                                       


                                             












































                                                                            
                                                             



                                                       
 
                                                                            





















































































                                                                           

                               


                                                     
         


















                                                                             
             














                                                                              

             


                                            
         


                                                              
             





                                                                                

             
                               
     




                          



                                                                          
                                          


                                 


                                          



                                   
                                                        



                                                             
                                




                                                      


                                          



                                   
                                                        



                                                                         
                                                                    
























































































































































































































































































                                                                             
/*
 *  File:       wiz-item.cc
 *  Summary:    Item related wizard functions.
 *  Written by: Linley Henzell and Jesse Jones
 */

#include "AppHdr.h"

#include "wiz-item.h"

#include <errno.h>

#include "artefact.h"
#include "coordit.h"
#include "message.h"
#include "cio.h"
#include "dbg-util.h"
#include "decks.h"
#include "effects.h"
#include "env.h"
#include "itemprop.h"
#include "items.h"
#include "item_use.h"
#include "it_use2.h"
#include "invent.h"
#include "makeitem.h"
#include "mon-iter.h"
#include "mon-stuff.h"
#include "mon-util.h"
#include "options.h"
#include "output.h"
#include "religion.h"
#include "skills2.h"
#include "spl-book.h"
#include "spl-util.h"
#include "stash.h"
#include "stuff.h"
#include "terrain.h"

#ifdef WIZARD
static void _make_all_books()
{
    for (int i = 0; i < NUM_FIXED_BOOKS; ++i)
    {
        int thing = items(0, OBJ_BOOKS, i, true, 0, MAKE_ITEM_NO_RACE,
                          0, 0, AQ_WIZMODE);
        if (thing == NON_ITEM)
            continue;

        move_item_to_grid(&thing, you.pos());

        if (thing == NON_ITEM)
            continue;

        item_def book(mitm[thing]);

        mark_had_book(book);
        set_ident_flags(book, ISFLAG_KNOW_TYPE);
        set_ident_flags(book, ISFLAG_IDENT_MASK);

        mprf("%s", book.name(DESC_PLAIN).c_str());
    }
}

//---------------------------------------------------------------
//
// create_spec_object
//
//---------------------------------------------------------------
void wizard_create_spec_object()
{
    char           specs[80];
    char           keyin;
    monster_type   mon;

    object_class_type class_wanted   = OBJ_UNASSIGNED;

    int            thing_created;

    while (class_wanted == OBJ_UNASSIGNED)
    {
        mpr(") - weapons     ( - missiles  [ - armour  / - wands    ?  - scrolls",
            MSGCH_PROMPT);
        mpr("= - jewellery   ! - potions   : - books   | - staves   0  - The Orb",
            MSGCH_PROMPT);
        mpr("} - miscellany  X - corpses   % - food    $ - gold    ESC - exit",
            MSGCH_PROMPT);

        mpr("What class of item? ", MSGCH_PROMPT);

        keyin = toupper( get_ch() );

        if (keyin == ')')
            class_wanted = OBJ_WEAPONS;
        else if (keyin == '(')
            class_wanted = OBJ_MISSILES;
        else if (keyin == '[' || keyin == ']')
            class_wanted = OBJ_ARMOUR;
        else if (keyin == '/' || keyin == '\\')
            class_wanted = OBJ_WANDS;
        else if (keyin == '?')
            class_wanted = OBJ_SCROLLS;
        else if (keyin == '=' || keyin == '"')
            class_wanted = OBJ_JEWELLERY;
        else if (keyin == '!')
            class_wanted = OBJ_POTIONS;
        else if (keyin == ':' || keyin == '+')
            class_wanted = OBJ_BOOKS;
        else if (keyin == '|')
            class_wanted = OBJ_STAVES;
        else if (keyin == '0' || keyin == 'O')
            class_wanted = OBJ_ORBS;
        else if (keyin == '}' || keyin == '{')
            class_wanted = OBJ_MISCELLANY;
        else if (keyin == 'X' || keyin == '&')
            class_wanted = OBJ_CORPSES;
        else if (keyin == '%')
            class_wanted = OBJ_FOOD;
        else if (keyin == '$')
            class_wanted = OBJ_GOLD;
        else if (keyin == ESCAPE || keyin == ' '
                || keyin == '\r' || keyin == '\n')
        {
            canned_msg( MSG_OK );
            return;
        }
    }

    // Allocate an item to play with.
    thing_created = get_item_slot();
    if (thing_created == NON_ITEM)
    {
        mpr("Could not allocate item.");
        return;
    }

    // turn item into appropriate kind:
    if (class_wanted == OBJ_ORBS)
    {
        mitm[thing_created].base_type = OBJ_ORBS;
        mitm[thing_created].sub_type  = ORB_ZOT;
        mitm[thing_created].quantity  = 1;
    }
    else if (class_wanted == OBJ_GOLD)
    {
        int amount = debug_prompt_for_int( "How much gold? ", true );
        if (amount <= 0)
        {
            canned_msg( MSG_OK );
            return;
        }

        mitm[thing_created].base_type = OBJ_GOLD;
        mitm[thing_created].sub_type  = 0;
        mitm[thing_created].quantity  = amount;
    }
    else if (class_wanted == OBJ_CORPSES)
    {
        mon = debug_prompt_for_monster();

        if (mon == MONS_NO_MONSTER || mon == MONS_PROGRAM_BUG)
        {
            mpr("No such monster.");
            return;
        }

        if (mons_weight(mon) <= 0)
        {
            if (!yesno("That monster doesn't leave corpses; make one "
                       "anyway?", true, 'y'))
            {
                return;
            }
        }

        if (mon >= MONS_DRACONIAN_CALLER && mon <= MONS_DRACONIAN_SCORCHER)
        {
            mpr("You can't make a draconian corpse by its job.");
            mon = MONS_DRACONIAN;
        }

        monsters dummy;
        dummy.type = mon;

        if (mons_genus(mon) == MONS_HYDRA)
            dummy.number = debug_prompt_for_int("How many heads? ", false);

        if (fill_out_corpse(&dummy, mitm[thing_created], true) == -1)
        {
            mpr("Failed to create corpse.");
            mitm[thing_created].clear();
            return;
        }
    }
    else
    {
        if (class_wanted == OBJ_BOOKS)
            mpr("What type of item? (\"all\" for all) ", MSGCH_PROMPT);
        else
            mpr("What type of item? ", MSGCH_PROMPT);
        get_input_line( specs, sizeof( specs ) );

        std::string temp = specs;
        trim_string(temp);
        lowercase(temp);
        strcpy(specs, temp.c_str());

        if (class_wanted == OBJ_BOOKS && temp == "all")
        {
            _make_all_books();
            return;
        }

        if (specs[0] == '\0')
        {
            canned_msg( MSG_OK );
            return;
        }

        if (!get_item_by_name(&mitm[thing_created], specs, class_wanted, true))
        {
            mpr("No such item.");

            // Clean up item
            destroy_item(thing_created);
            return;
        }
        if (Options.autoinscribe_artefacts && is_artefact(mitm[thing_created]))
        {
            mitm[thing_created].inscription
                = artefact_auto_inscription(mitm[thing_created]);
        }
    }

    // Deck colour (which control rarity) already set.
    if (!is_deck(mitm[thing_created]))
        item_colour( mitm[thing_created] );

    move_item_to_grid( &thing_created, you.pos() );

    if (thing_created != NON_ITEM)
    {
        // orig_monnum is used in corpses for things like the Animate
        // Dead spell, so leave it alone.
        if (class_wanted != OBJ_CORPSES)
            origin_acquired(mitm[thing_created], AQ_WIZMODE);
        canned_msg(MSG_SOMETHING_APPEARS);

        // Tell the stash tracker.
        maybe_update_stashes();
    }
}

const char* _prop_name[ARTP_NUM_PROPERTIES] = {
    "Brand",
    "AC",
    "EV",
    "Str",
    "Int",
    "Dex",
    "Fire",
    "Cold",
    "Elec",
    "Pois",
    "Neg",
    "Mag",
    "SInv",
    "Inv",
    "Lev",
    "Blnk",
    "Bers",
    "Nois",
    "NoSpl",
    "RndTl",
    "NoTel",
    "Anger",
    "Metab",
    "Mut",
    "Acc",
    "Dam",
    "Curse",
    "Stlth",
    "MP"
};

#define ARTP_VAL_BOOL 0
#define ARTP_VAL_POS  1
#define ARTP_VAL_ANY  2

char _prop_type[ARTP_NUM_PROPERTIES] = {
    ARTP_VAL_POS,  //BRAND
    ARTP_VAL_ANY,  //AC
    ARTP_VAL_ANY,  //EVASION
    ARTP_VAL_ANY,  //STRENGTH
    ARTP_VAL_ANY,  //INTELLIGENCE
    ARTP_VAL_ANY,  //DEXTERITY
    ARTP_VAL_ANY,  //FIRE
    ARTP_VAL_ANY,  //COLD
    ARTP_VAL_BOOL, //ELECTRICITY
    ARTP_VAL_BOOL, //POISON
    ARTP_VAL_BOOL, //NEGATIVE_ENERGY
    ARTP_VAL_POS,  //MAGIC
    ARTP_VAL_BOOL, //EYESIGHT
    ARTP_VAL_BOOL, //INVISIBLE
    ARTP_VAL_BOOL, //LEVITATE
    ARTP_VAL_BOOL, //BLINK
    ARTP_VAL_BOOL, //BERSERK
    ARTP_VAL_POS,  //NOISES
    ARTP_VAL_BOOL, //PREVENT_SPELLCASTING
    ARTP_VAL_BOOL, //CAUSE_TELEPORTATION
    ARTP_VAL_BOOL, //PREVENT_TELEPORTATION
    ARTP_VAL_POS,  //ANGRY
    ARTP_VAL_POS,  //METABOLISM
    ARTP_VAL_POS,  //MUTAGENIC
    ARTP_VAL_ANY,  //ACCURACY
    ARTP_VAL_ANY,  //DAMAGE
    ARTP_VAL_POS,  //CURSED
    ARTP_VAL_ANY,  //STEALTH
    ARTP_VAL_ANY   //MAGICAL_POWER
};

static void _tweak_randart(item_def &item)
{
    if (item_is_equipped(item))
    {
        mpr("You can't tweak the randart properties of an equipped item.",
            MSGCH_PROMPT);
        return;
    }
    else
        mesclr();

    artefact_properties_t props;
    artefact_wpn_properties(item, props);

    std::string prompt = "";

    std::vector<unsigned int> choice_to_prop;
    for (unsigned int i = 0, choice_num = 0; i < ARTP_NUM_PROPERTIES; ++i)
    {
        if (_prop_name[i] == std::string("UNUSED"))
            continue;
        choice_to_prop.push_back(i);
        if (choice_num % 8 == 0 && choice_num != 0)
            prompt += "\n";

        char choice;
        char buf[80];

        if (choice_num < 26)
            choice = 'A' + choice_num;
        else
            choice = '1' + choice_num - 26;

        if (props[i])
            sprintf(buf, "%c) <w>%-5s</w> ", choice, _prop_name[i]);
        else
            sprintf(buf, "%c) %-5s ", choice, _prop_name[i]);

        prompt += buf;

        choice_num++;
    }
    formatted_message_history(prompt, MSGCH_PROMPT, 0, 80);

    mpr("Change which field? ", MSGCH_PROMPT);

    char     keyin = tolower( get_ch() );
    unsigned int  choice;

    if (isalpha(keyin))
        choice = keyin - 'a';
    else if (isdigit(keyin) && keyin != '0')
        choice = keyin - '1' + 26;
    else
        return;

    if (choice >= choice_to_prop.size())
        return;

    unsigned int prop = choice_to_prop[choice];
    ASSERT(prop >= 0);
    ASSERT(prop < ARRAYSZ(_prop_type));

    int val;
    switch (_prop_type[prop])
    {
    case ARTP_VAL_BOOL:
        mprf(MSGCH_PROMPT, "Toggling %s to %s.", _prop_name[prop],
             props[prop] ? "off" : "on");
        artefact_set_property(item, static_cast<artefact_prop_type>(prop),
                             !props[prop]);
        break;

    case ARTP_VAL_POS:
        mprf(MSGCH_PROMPT, "%s was %d.", _prop_name[prop], props[prop]);
        val = debug_prompt_for_int("New value? ", true);

        if (val < 0)
        {
            mprf(MSGCH_PROMPT, "Value for %s must be non-negative",
                 _prop_name[prop]);
            return;
        }
        artefact_set_property(item, static_cast<artefact_prop_type>(prop),
                             val);
        break;
    case ARTP_VAL_ANY:
        mprf(MSGCH_PROMPT, "%s was %d.", _prop_name[prop], props[prop]);
        val = debug_prompt_for_int("New value? ", false);
        artefact_set_property(item, static_cast<artefact_prop_type>(prop),
                             val);
        break;
    }

    if (Options.autoinscribe_artefacts)
        item.inscription = artefact_auto_inscription(item);
}

void wizard_tweak_object(void)
{
    char specs[50];
    char keyin;

    int item = prompt_invent_item("Tweak which item? ", MT_INVLIST, -1);
    if (item == PROMPT_ABORT)
    {
        canned_msg( MSG_OK );
        return;
    }

    if (item == you.equip[EQ_WEAPON])
        you.wield_change = true;

    const bool is_art = is_artefact(you.inv[item]);

    while (true)
    {
        void *field_ptr = NULL;

        while (true)
        {
            mpr(you.inv[item].name(DESC_INVENTORY_EQUIP).c_str());

            if (is_art)
            {
                mpr("a - plus  b - plus2  c - art props  d - quantity  "
                    "e - flags  ESC - exit", MSGCH_PROMPT);
            }
            else
            {
                mpr("a - plus  b - plus2  c - special  d - quantity  "
                    "e - flags  ESC - exit", MSGCH_PROMPT);
            }

            mpr("Which field? ", MSGCH_PROMPT);

            keyin = tolower( get_ch() );

            if (keyin == 'a')
                field_ptr = &(you.inv[item].plus);
            else if (keyin == 'b')
                field_ptr = &(you.inv[item].plus2);
            else if (keyin == 'c')
                field_ptr = &(you.inv[item].special);
            else if (keyin == 'd')
                field_ptr = &(you.inv[item].quantity);
            else if (keyin == 'e')
                field_ptr = &(you.inv[item].flags);
            else if (keyin == ESCAPE || keyin == ' '
                    || keyin == '\r' || keyin == '\n')
            {
                canned_msg( MSG_OK );
                return;
            }

            if (keyin >= 'a' && keyin <= 'e')
                break;
        }

        if (is_art && keyin == 'c')
        {
            _tweak_randart(you.inv[item]);
            continue;
        }

        if (keyin != 'c' && keyin != 'e')
        {
            const short *const ptr = static_cast< short * >( field_ptr );
            mprf("Old value: %d (0x%04x)", *ptr, *ptr );
        }
        else
        {
            const long *const ptr = static_cast< long * >( field_ptr );
            mprf("Old value: %ld (0x%08lx)", *ptr, *ptr );
        }

        mpr("New value? ", MSGCH_PROMPT);
        get_input_line( specs, sizeof( specs ) );

        if (specs[0] == '\0')
            return;

        char *end;
        const bool hex = (keyin == 'e');
        int   new_value = strtol(specs, &end, hex ? 16 : 0);

        if (new_value == 0 && end == specs)
            return;

        if (keyin != 'c' && keyin != 'e')
        {
            short *ptr = static_cast< short * >( field_ptr );
            *ptr = new_value;
        }
        else
        {
            long *ptr = static_cast< long * >( field_ptr );
            *ptr = new_value;
        }
    }
}

// Returns whether an item of this type can be an artefact.
static bool _item_type_can_be_artefact( int type)
{
    return (type == OBJ_WEAPONS || type == OBJ_ARMOUR || type == OBJ_JEWELLERY
            || type == OBJ_BOOKS);
}

static bool _make_book_randart(item_def &book)
{
    char type;

    do
    {
        mpr("Make book fixed [t]heme or fixed [l]evel? ", MSGCH_PROMPT);
        type = tolower(getch());
    }
    while (type != 't' && type != 'l');

    if (type == 'l')
        return make_book_level_randart(book);
    else
        return make_book_theme_randart(book);
}

void wizard_value_artefact()
{
    int i = prompt_invent_item( "Value of which artefact?", MT_INVLIST, -1 );

    if (!prompt_failed(i))
    {
        const item_def& item(you.inv[i]);
        if (!is_artefact(item))
            mpr("That item is not an artefact!");
        else
            mprf("%s", debug_art_val_str(item).c_str());
    }
}

void wizard_create_all_artefacts()
{
    // Create all unrandarts.
    for (int i = 0; i < NO_UNRANDARTS; ++i)
    {
        const int              index = i + UNRAND_START;
        const unrandart_entry* entry = get_unrand_entry(index);

        // Skip dummy entries.
        if (entry->base_type == OBJ_UNASSIGNED)
            continue;

        int islot = get_item_slot();
        if (islot == NON_ITEM)
            break;

        item_def& item = mitm[islot];
        make_item_unrandart(item, index);
        item.quantity = 1;
        set_ident_flags(item, ISFLAG_IDENT_MASK);

        msg::streams(MSGCH_DIAGNOSTICS) << "Made " << item.name(DESC_NOCAP_A)
                                        << " (" << debug_art_val_str(item)
                                        << ")" << std::endl;
        move_item_to_grid(&islot, you.pos());
    }

    // Create Horn of Geryon
    int islot = get_item_slot();
    if (islot != NON_ITEM)
    {
        item_def& item = mitm[islot];
        item.clear();
        item.base_type = OBJ_MISCELLANY;
        item.sub_type  = MISC_HORN_OF_GERYON;
        item.quantity  = 1;
        item_colour(item);

        set_ident_flags(item, ISFLAG_IDENT_MASK);
        move_item_to_grid(&islot, you.pos());

        msg::streams(MSGCH_DIAGNOSTICS) << "Made " << item.name(DESC_NOCAP_A)
                                        << std::endl;
    }
}

void wizard_make_object_randart()
{
    int i = prompt_invent_item( "Make an artefact out of which item?",
                                MT_INVLIST, -1 );

    if (prompt_failed(i))
        return;

    item_def &item(you.inv[i]);

    if (is_unrandom_artefact(item))
    {
        mpr("That item is already an unrandom artefact.");
        return;
    }

    if (!_item_type_can_be_artefact(item.base_type))
    {
        mpr("That item cannot be turned into an artefact.");
        return;
    }

    if (you.weapon() == &item)
        you.wield_change = true;

    if (is_random_artefact(item))
    {
        if (!yesno("Is already a randart; wipe and re-use?", true, 'n'))
        {
            canned_msg(MSG_OK);
            return;
        }

        if (item_is_equipped(item))
            unuse_artefact(item);

        item.special = 0;
        item.flags  &= ~ISFLAG_RANDART;
        item.props.clear();
    }

    mpr("Fake item as gift from which god (ENTER to leave alone): ",
        MSGCH_PROMPT);
    char name[80];
    if (!cancelable_get_line(name, sizeof( name )) && name[0])
    {
        god_type god = string_to_god(name, false);
        if (god == GOD_NO_GOD)
           mpr("No such god, leaving item origin alone.");
        else
        {
           mprf("God gift of %s.", god_name(god, false).c_str());
           item.orig_monnum = -god;
        }
    }

    if (item.base_type == OBJ_BOOKS)
    {
        if (!_make_book_randart(item))
        {
            mpr("Failed to turn book into randart.");
            return;
        }
    }
    else if (!make_item_randart(item))
    {
        mpr("Failed to turn item into randart.");
        return;
    }

    if (Options.autoinscribe_artefacts)
        add_autoinscription(item, artefact_auto_inscription(you.inv[i]));

    // If equipped, apply new randart benefits.
    if (item_is_equipped(item))
        use_artefact(item);

    mpr(item.name(DESC_INVENTORY_EQUIP).c_str());
}

// Returns whether an item of this type can be cursed.
static bool _item_type_can_be_cursed(int type)
{
    return (type == OBJ_WEAPONS || type == OBJ_ARMOUR || type == OBJ_JEWELLERY);
}

void wizard_uncurse_item()
{
    const int i = prompt_invent_item("(Un)curse which item?", MT_INVLIST, -1);

    if (!prompt_failed(i))
    {
        item_def& item(you.inv[i]);

        if (item.cursed())
            do_uncurse_item(item);
        else if (_item_type_can_be_cursed(item.base_type))
            do_curse_item(item);
        else
            mpr("That type of item cannot be cursed.");
    }
}

void wizard_identify_pack()
{
    mpr("You feel a rush of knowledge.");
    for (int i = 0; i < ENDOFPACK; ++i)
    {
        item_def& item = you.inv[i];
        if (item.is_valid())
        {
            set_ident_type(item, ID_KNOWN_TYPE);
            set_ident_flags(item, ISFLAG_IDENT_MASK);
        }
    }
    you.wield_change  = true;
    you.redraw_quiver = true;
}

void wizard_unidentify_pack()
{
    mpr("You feel a rush of antiknowledge.");
    for (int i = 0; i < ENDOFPACK; ++i)
    {
        item_def& item = you.inv[i];
        if (item.is_valid())
        {
            set_ident_type(item, ID_UNKNOWN_TYPE);
            unset_ident_flags(item, ISFLAG_IDENT_MASK);
        }
    }
    you.wield_change  = true;
    you.redraw_quiver = true;

    // Forget things that nearby monsters are carrying, as well.
    // (For use with the "give monster an item" wizard targetting
    // command.)
    for (monster_iterator mon(&you.get_los()); mon; ++mon)
    {
        for (int j = 0; j < NUM_MONSTER_SLOTS; ++j)
        {
            if (mon->inv[j] == NON_ITEM)
                continue;

            item_def &item = mitm[mon->inv[j]];

            if (!item.is_valid())
                continue;

            set_ident_type(item, ID_UNKNOWN_TYPE);
            unset_ident_flags(item, ISFLAG_IDENT_MASK);
        }
    }
}

void wizard_list_items()
{
    bool has_shops = false;

    for (int i = 0; i < MAX_SHOPS; ++i)
        if (env.shop[i].type != SHOP_UNASSIGNED)
        {
            has_shops = true;
            break;
        }

    if (has_shops)
    {
        mpr("Shop items:");

        for (int i = 0; i < MAX_SHOPS; ++i)
            if (env.shop[i].type != SHOP_UNASSIGNED)
            {
                for (stack_iterator si(coord_def(0, i+5)); si; ++si)
                    mpr(si->name(DESC_PLAIN, false, false, false).c_str());
            }

        mpr(EOL);
    }

    mpr("Item stacks (by location and top item):");
    for (int i = 0; i < MAX_ITEMS; ++i)
    {
        item_def &item(mitm[i]);
        if (!item.is_valid() || item.held_by_monster())
            continue;

        if (item.link != NON_ITEM)
        {
            mprf("(%2d,%2d): %s", item.pos.x, item.pos.y,
                 item.name(DESC_PLAIN, false, false, false).c_str() );
        }
    }

    mpr(EOL);
    mpr("Floor items (stacks only show top item):");

    const coord_def start(1,1), end(GXM-1, GYM-1);
    for (rectangle_iterator ri(start, end); ri; ++ri)
    {
        int item = igrd(*ri);
        if (item != NON_ITEM)
        {
            mprf("%3d at (%2d,%2d): %s", item, ri->x, ri->y,
                 mitm[item].name(DESC_PLAIN, false, false, false).c_str());
        }
    }
}

//---------------------------------------------------------------
//
// debug_item_statistics
//
//---------------------------------------------------------------
static void _debug_acquirement_stats(FILE *ostat)
{
    int p = get_item_slot(11);
    if (p == NON_ITEM)
    {
        mpr("Too many items on level.");
        return;
    }
    mitm[p].base_type = OBJ_UNASSIGNED;

    mesclr();
    mpr("[a] Weapons [b] Armours [c] Jewellery      [d] Books");
    mpr("[e] Staves  [f] Wands   [g] Miscellaneous  [h] Food");
    mpr("What kind of item would you like to get acquirement stats on? ",
        MSGCH_PROMPT);

    object_class_type type;
    const int keyin = tolower( get_ch() );
    switch ( keyin )
    {
    case 'a': type = OBJ_WEAPONS;    break;
    case 'b': type = OBJ_ARMOUR;     break;
    case 'c': type = OBJ_JEWELLERY;  break;
    case 'd': type = OBJ_BOOKS;      break;
    case 'e': type = OBJ_STAVES;     break;
    case 'f': type = OBJ_WANDS;      break;
    case 'g': type = OBJ_MISCELLANY; break;
    case 'h': type = OBJ_FOOD;       break;
    default:
        canned_msg( MSG_OK );
        return;
    }

    const int num_itrs = debug_prompt_for_int("How many iterations? ", true);

    if (num_itrs == 0)
    {
        canned_msg( MSG_OK );
        return;
    }

    int last_percent = 0;
    int acq_calls    = 0;
    int total_quant  = 0;
    int max_plus     = -127;
    int total_plus   = 0;
    int num_arts     = 0;

    int subtype_quants[256];
    int ego_quants[SPWPN_DEBUG_RANDART];

    memset(subtype_quants, 0, sizeof(subtype_quants));
    memset(ego_quants, 0, sizeof(ego_quants));

    for (int i = 0; i < num_itrs; ++i)
    {
        if (kbhit())
        {
            getch();
            mpr("Stopping early due to keyboard input.");
            break;
        }

        int item_index = NON_ITEM;

        if (!acquirement(type, AQ_WIZMODE, true, &item_index, true)
            || item_index == NON_ITEM
            || !mitm[item_index].is_valid())
        {
            mpr("Acquirement failed, stopping early.");
            break;
        }

        item_def &item(mitm[item_index]);

        acq_calls++;
        total_quant += item.quantity;
        subtype_quants[item.sub_type] += item.quantity;

        max_plus    = std::max(max_plus, item.plus + item.plus2);
        total_plus += item.plus + item.plus2;

        if (is_artefact(item))
        {
            num_arts++;
            if (type == OBJ_BOOKS)
            {
                if (item.sub_type == BOOK_RANDART_THEME)
                {
                    const int disc1 = item.plus & 0xFF;
                    ego_quants[disc1]++;
                }
                else if (item.sub_type == BOOK_RANDART_LEVEL)
                {
                    const int level = item.plus;
                    ego_quants[SPTYP_LAST_EXPONENT + level]++;
                }
            }
        }
        else if (type == OBJ_ARMOUR) // Exclude artefacts when counting egos.
            ego_quants[get_armour_ego_type(item)]++;
        else if (type == OBJ_BOOKS && item.sub_type == BOOK_MANUAL)
        {
            // Store skills in subtype array, so as not to overlap
            // with artefact spell disciplines/levels.
            const int skill = item.plus;
            subtype_quants[200 + skill]++;
        }

        // Include artefacts for weapon brands.
        if (type == OBJ_WEAPONS)
            ego_quants[get_weapon_brand(item)]++;

        destroy_item(item_index, true);

        int curr_percent = acq_calls * 100 / num_itrs;
        if (curr_percent > last_percent)
        {
            mesclr();
            mprf("%2d%% done.", curr_percent);
            last_percent = curr_percent;
        }
    }

    if (total_quant == 0 || acq_calls == 0)
    {
        mpr("No items generated.");
        return;
    }

    // Print acquirement base type.
    fprintf(ostat, "Acquiring %s for:\n\n",
            type == OBJ_WEAPONS    ? "weapons" :
            type == OBJ_ARMOUR     ? "armour"  :
            type == OBJ_JEWELLERY  ? "jewellery" :
            type == OBJ_BOOKS      ? "books" :
            type == OBJ_STAVES     ? "staves" :
            type == OBJ_WANDS      ? "wands" :
            type == OBJ_MISCELLANY ? "misc. items" :
            type == OBJ_FOOD       ? "food"
                                   : "buggy items");

    // Print player species/profession.
    std::string godname = "";
    if (you.religion != GOD_NO_GOD)
        godname += " of " + god_name(you.religion);

    fprintf(ostat, "%s the %s, Level %d %s %s%s\n\n",
            you.your_name.c_str(), player_title().c_str(),
            you.experience_level,
            species_name(you.species, you.experience_level).c_str(),
            you.class_name, godname.c_str());

    // Print player equipment.
    const int e_order[] =
    {
        EQ_WEAPON, EQ_BODY_ARMOUR, EQ_SHIELD, EQ_HELMET, EQ_CLOAK,
        EQ_GLOVES, EQ_BOOTS, EQ_AMULET, EQ_RIGHT_RING, EQ_LEFT_RING
    };

    bool naked = true;
    for (int i = 0; i < NUM_EQUIP; i++)
    {
        int eqslot = e_order[i];

        // Only output filled slots.
        if (you.equip[ e_order[i] ] != -1)
        {
            // The player has something equipped.
            const int item_idx   = you.equip[e_order[i]];
            const item_def& item = you.inv[item_idx];
            const bool melded    = !player_wearing_slot(e_order[i]);

            fprintf(ostat, "%-7s: %s %s\n", equip_slot_to_name(eqslot),
                    item.name(DESC_PLAIN, true).c_str(),
                    melded ? "(melded)" : "");
            naked = false;
        }
    }
    if (naked)
        fprintf(ostat, "Not wearing or wielding anything.\n");

    // Also print the skills, in case they matter.
    std::string skills = "\nSkills:\n";
    dump_skills(skills);
    fprintf(ostat, "%s\n\n", skills.c_str());

    if (type == OBJ_BOOKS && you.skills[SK_SPELLCASTING])
    {
        // For spellbooks, for each spell discipline, list the number of
        // unseen and total spells available.
        std::vector<int> total_spells(SPTYP_LAST_EXPONENT);
        std::vector<int> unseen_spells(SPTYP_LAST_EXPONENT);

        for (int i = 0; i < NUM_SPELLS; ++i)
        {
            const spell_type spell = (spell_type) i;

            if (!is_valid_spell(spell))
                continue;

            if (you_cannot_memorise(spell))
                continue;

            // Only use spells available in books you might find lying about
            // the dungeon.
            if (spell_rarity(spell) == -1)
                continue;

            const bool seen = you.seen_spell[spell];

            const unsigned int disciplines = get_spell_disciplines(spell);
            for (int d = 0; d < SPTYP_LAST_EXPONENT; ++d)
            {
                const int disc = 1 << d;
                if (disc & SPTYP_DIVINATION)
                    continue;

                if (disciplines & disc)
                {
                    total_spells[d]++;
                    if (!seen)
                        unseen_spells[d]++;
                }
            }
        }
        for (int d = 0; d < SPTYP_LAST_EXPONENT; ++d)
        {
            const int disc = 1 << d;
            if (disc & SPTYP_DIVINATION)
                continue;

            fprintf(ostat, "%-13s:  %2d/%2d spells unseen\n",
                    spelltype_long_name(disc),
                    unseen_spells[d], total_spells[d]);
        }
    }

    fprintf(ostat, "\nAcquirement called %d times, total quantity = %d\n\n",
            acq_calls, total_quant);

    fprintf(ostat, "%5.2f%% artefacts.\n",
            100.0 * (float) num_arts / (float) acq_calls);

    if (type == OBJ_WEAPONS)
    {
        fprintf(ostat, "Maximum combined pluses: %d\n", max_plus);
        fprintf(ostat, "Average combined pluses: %5.2f\n\n",
                (float) total_plus / (float) acq_calls);

        fprintf(ostat, "Egos (including artefacts):\n");

        const char* names[] = {
            "normal",
            "flaming",
            "freezing",
            "holy wrath",
            "electrocution",
            "orc slaying",
            "dragon slaying",
            "venom",
            "protection",
            "draining",
            "speed",
            "vorpal",
            "flame",
            "frost",
            "vampiricism",
            "pain",
            "distortion",
            "reaching",
            "returning",
            "chaos",
            "confusion",
        };

        for (int i = 0; i <= SPWPN_CONFUSE; ++i)
            if (ego_quants[i] > 0)
            {
                fprintf(ostat, "%14s: %5.2f\n", names[i],
                        100.0 * (float) ego_quants[i] / (float) acq_calls);
            }

        fprintf(ostat, "\n\n");
    }
    else if (type == OBJ_ARMOUR)
    {
        fprintf(ostat, "Maximum plus: %d\n", max_plus);
        fprintf(ostat, "Average plus: %5.2f\n\n",
                (float) total_plus / (float) acq_calls);

        fprintf(ostat, "Egos (excluding artefacts):\n");

        const char* names[] = {
            "normal",
            "running",
            "fire resistance",
            "cold resistance",
            "poison resistance",
            "see invis",
            "darkness",
            "strength",
            "dexterity",
            "intelligence",
            "ponderous",
            "levitation",
            "magic reistance",
            "protection",
            "stealth",
            "resistance",
            "positive energy",
            "archmagi",
            "preservation",
            "reflection"
         };

        const int non_art = acq_calls - num_arts;
        for (int i = 0; i <= SPARM_REFLECTION; ++i)
        {
           if (ego_quants[i] > 0)
               fprintf(ostat, "%17s: %5.2f\n", names[i],
                       100.0 * (float) ego_quants[i] / (float) non_art);
        }
        fprintf(ostat, "\n\n");
    }
    else if (type == OBJ_BOOKS)
    {
        // Print disciplines of artefact spellbooks.
        if (subtype_quants[BOOK_RANDART_THEME]
            + subtype_quants[BOOK_RANDART_LEVEL] > 0)
        {
            fprintf(ostat, "Primary disciplines/levels of randart books:\n");

            const char* names[] = {
                "conjuration",
                "enchantment",
                "fire magic",
                "ice magic",
                "transmutation",
                "necromancy",
                "summoning",
                "divination",
                "translocation",
                "poison magic",
                "earth magic",
                "air magic",
                "holy magic"
            };

            for (int i = 0; i < SPTYP_LAST_EXPONENT; ++i)
            {
                if (ego_quants[i] > 0)
                {
                    fprintf(ostat, "%17s: %5.2f\n", names[i],
                            100.0 * (float) ego_quants[i] / (float) num_arts);
                }
            }
            // List levels for fixed level randarts.
            for (int i = 1; i < 9; ++i)
            {
                const int k = SPTYP_LAST_EXPONENT + i;
                if (ego_quants[k] > 0)
                {
                    fprintf(ostat, "%15s %d: %5.2f\n", "level", i,
                            100.0 * (float) ego_quants[i] / (float) num_arts);
                }
            }
        }

        // Also list skills for manuals.
        if (subtype_quants[BOOK_MANUAL] > 0)
        {
            const int mannum = subtype_quants[BOOK_MANUAL];
            fprintf(ostat, "\nManuals:\n");
            for (int i = SK_FIGHTING; i <= SK_EVOCATIONS; ++i)
            {
                const int k = 200 + i;
                if (subtype_quants[k] > 0)
                {
                    fprintf(ostat, "%17s: %5.2f\n", skill_name(i),
                            100.0 * (float) subtype_quants[k] / (float) mannum);
                }
            }
        }
        fprintf(ostat, "\n\n");
    }

    item_def item;
    item.quantity  = 1;
    item.base_type = type;

    const description_level_type desc = (type == OBJ_BOOKS ? DESC_PLAIN
                                                           : DESC_DBNAME);
    const bool terse = (type == OBJ_BOOKS ? false : true);

    // First, get the maximum name length.
    int max_width = 0;
    for (int i = 0; i < 256; ++i)
    {
        if (type == OBJ_BOOKS && i >= 200)
            break;

        if (subtype_quants[i] == 0)
            continue;

        item.sub_type = i;
        std::string name = item.name(desc, terse, true);

        max_width = std::max(max_width, (int) name.length());
    }

    // Now output the sub types.
    char format_str[80];
    sprintf(format_str, "%%%ds: %%6.2f\n", max_width);

    for (int i = 0; i < 256; ++i)
    {
        if (type == OBJ_BOOKS && i >= 200)
            break;

        if (subtype_quants[i] == 0)
            continue;

        item.sub_type = i;
        std::string name = item.name(desc, terse, true);

        fprintf(ostat, format_str, name.c_str(),
                (float) subtype_quants[i] * 100.0 / (float) total_quant);
    }
    fprintf(ostat, "-----------------------------------------\n\n");

    mpr("Results written into 'items.stat'.");
}

static void _debug_rap_stats(FILE *ostat)
{
    int i = prompt_invent_item( "Generate randart stats on which item?",
                                MT_INVLIST, -1 );

    if (i == PROMPT_ABORT)
    {
        canned_msg( MSG_OK );
        return;
    }

    // A copy of the item, rather than a reference to the inventory item,
    // so we can fiddle with the item at will.
    item_def item(you.inv[i]);

    // Start off with a non-artefact item.
    item.flags  &= ~ISFLAG_ARTEFACT_MASK;
    item.special = 0;
    item.props.clear();

    if (!make_item_randart(item))
    {
        mpr("Can't make a randart out of that type of item.");
        return;
    }

    // -1 = always bad, 1 = always good, 0 = depends on value
    const int good_or_bad[] = {
         1, //ARTP_BRAND
         0, //ARTP_AC
         0, //ARTP_EVASION
         0, //ARTP_STRENGTH
         0, //ARTP_INTELLIGENCE
         0, //ARTP_DEXTERITY
         0, //ARTP_FIRE
         0, //ARTP_COLD
         1, //ARTP_ELECTRICITY
         1, //ARTP_POISON
         1, //ARTP_NEGATIVE_ENERGY
         1, //ARTP_MAGIC
         1, //ARTP_EYESIGHT
         1, //ARTP_INVISIBLE
         1, //ARTP_LEVITATE
         1, //ARTP_BLINK
         1, //ARTP_CAN_TELEPORT
         1, //ARTP_BERSERK
         1, //ARTP_UNUSED_1
        -1, //ARTP_NOISES
        -1, //ARTP_PREVENT_SPELLCASTING
        -1, //ARTP_CAUSE_TELEPORTATION
        -1, //ARTP_PREVENT_TELEPORTATION
        -1, //ARTP_ANGRY
        -1, //ARTP_METABOLISM
        -1, //ARTP_MUTAGENIC
         0, //ARTP_ACCURACY
         0, //ARTP_DAMAGE
        -1, //ARTP_CURSED
         0, //ARTP_STEALTH
         0  //ARTP_MAGICAL_POWER
    };

    // No bounds checking to speed things up a bit.
    int all_props[ARTP_NUM_PROPERTIES];
    int good_props[ARTP_NUM_PROPERTIES];
    int bad_props[ARTP_NUM_PROPERTIES];
    for (i = 0; i < ARTP_NUM_PROPERTIES; ++i)
    {
        all_props[i] = 0;
        good_props[i] = 0;
        bad_props[i] = 0;
    }

    int max_props         = 0, total_props         = 0;
    int max_good_props    = 0, total_good_props    = 0;
    int max_bad_props     = 0, total_bad_props     = 0;
    int max_balance_props = 0, total_balance_props = 0;

    int num_randarts = 0, bad_randarts = 0;

    artefact_properties_t proprt;

    for (i = 0; i < RANDART_SEED_MASK; ++i)
    {
        if (kbhit())
        {
            getch();
            mpr("Stopping early due to keyboard input.");
            break;
        }

        item.special = i;

        // Generate proprt once and hand it off to randart_is_bad(),
        // so that randart_is_bad() doesn't generate it a second time.
        artefact_wpn_properties( item, proprt );
        if (randart_is_bad(item, proprt))
        {
            bad_randarts++;
            continue;
        }

        num_randarts++;
        proprt[ARTP_CURSED] = 0;

        int num_props = 0, num_good_props = 0, num_bad_props = 0;
        for (int j = 0; j < ARTP_NUM_PROPERTIES; ++j)
        {
            const int val = proprt[j];
            if (val)
            {
                num_props++;
                all_props[j]++;
                switch (good_or_bad[j])
                {
                case -1:
                    num_bad_props++;
                    break;
                case 1:
                    num_good_props++;
                    break;
                case 0:
                    if (val > 0)
                    {
                        good_props[j]++;
                        num_good_props++;
                    }
                    else
                    {
                        bad_props[j]++;
                        num_bad_props++;
                    }
                }
            }
        }

        int balance = num_good_props - num_bad_props;

        max_props         = std::max(max_props, num_props);
        max_good_props    = std::max(max_good_props, num_good_props);
        max_bad_props     = std::max(max_bad_props, num_bad_props);
        max_balance_props = std::max(max_balance_props, balance);

        total_props         += num_props;
        total_good_props    += num_good_props;
        total_bad_props     += num_bad_props;
        total_balance_props += balance;

        if (i % 16777 == 0)
        {
            mesclr();
            float curr_percent = (float) i * 1000.0
                / (float) RANDART_SEED_MASK;
            mprf("%4.1f%% done.", curr_percent / 10.0);
        }

    }

    fprintf(ostat, "Randarts generated: %d valid, %d invalid\n\n",
            num_randarts, bad_randarts);

    fprintf(ostat, "max # of props = %d, avg # = %5.2f\n",
            max_props, (float) total_props / (float) num_randarts);
    fprintf(ostat, "max # of good props = %d, avg # = %5.2f\n",
            max_good_props, (float) total_good_props / (float) num_randarts);
    fprintf(ostat, "max # of bad props = %d, avg # = %5.2f\n",
            max_bad_props, (float) total_bad_props / (float) num_randarts);
    fprintf(ostat, "max (good - bad) props = %d, avg # = %5.2f\n\n",
            max_balance_props,
            (float) total_balance_props / (float) num_randarts);

    const char* rap_names[] = {
        "ARTP_BRAND",
        "ARTP_AC",
        "ARTP_EVASION",
        "ARTP_STRENGTH",
        "ARTP_INTELLIGENCE",
        "ARTP_DEXTERITY",
        "ARTP_FIRE",
        "ARTP_COLD",
        "ARTP_ELECTRICITY",
        "ARTP_POISON",
        "ARTP_NEGATIVE_ENERGY",
        "ARTP_MAGIC",
        "ARTP_EYESIGHT",
        "ARTP_INVISIBLE",
        "ARTP_LEVITATE",
        "ARTP_BLINK",
        "ARTP_BERSERK",
        "ARTP_NOISES",
        "ARTP_PREVENT_SPELLCASTING",
        "ARTP_CAUSE_TELEPORTATION",
        "ARTP_PREVENT_TELEPORTATION",
        "ARTP_ANGRY",
        "ARTP_METABOLISM",
        "ARTP_MUTAGENIC",
        "ARTP_ACCURACY",
        "ARTP_DAMAGE",
        "ARTP_CURSED",
        "ARTP_STEALTH",
        "ARTP_MAGICAL_POWER"
    };

    fprintf(ostat, "                            All    Good   Bad\n");
    fprintf(ostat, "                           --------------------\n");

    for (i = 0; i < ARTP_NUM_PROPERTIES; ++i)
    {
        if (all_props[i] == 0)
            continue;

        fprintf(ostat, "%-25s: %5.2f%% %5.2f%% %5.2f%%\n", rap_names[i],
                (float) all_props[i] * 100.0 / (float) num_randarts,
                (float) good_props[i] * 100.0 / (float) num_randarts,
                (float) bad_props[i] * 100.0 / (float) num_randarts);
    }

    fprintf(ostat, "\n-----------------------------------------\n\n");
    mpr("Results written into 'items.stat'.");
}

void debug_item_statistics( void )
{
    FILE *ostat = fopen("items.stat", "a");

    if (!ostat)
    {
        mprf(MSGCH_ERROR, "Can't write items.stat: %s", strerror(errno));
        return;
    }

    mpr("Generate stats for: [a] acquirement [b] randart properties");

    const int keyin = tolower( get_ch() );
    switch ( keyin )
    {
    case 'a': _debug_acquirement_stats(ostat); break;
    case 'b': _debug_rap_stats(ostat);
    default:
        canned_msg( MSG_OK );
        break;
    }

    fclose(ostat);
}

void wizard_draw_card()
{
    msg::streams(MSGCH_PROMPT) << "Which card? " << std::endl;
    char buf[80];
    if (cancelable_get_line_autohist(buf, sizeof buf))
    {
        mpr("Unknown card.");
        return;
    }

    std::string wanted = buf;
    lowercase(wanted);

    bool found_card = false;
    for ( int i = 0; i < NUM_CARDS; ++i )
    {
        const card_type c = static_cast<card_type>(i);
        std::string card = card_name(c);
        lowercase(card);
        if ( card.find(wanted) != std::string::npos )
        {
            card_effect(c, DECK_RARITY_LEGENDARY);
            found_card = true;
            break;
        }
    }
    if (!found_card)
        mpr("Unknown card.");
}
#endif