/**
 * @file
 * @brief Item creation routines.
**/

#include "AppHdr.h"

#include "makeitem.h"

#include <algorithm>

#include "acquire.h"
#include "art-enum.h" // unrand -> magic staff silliness
#include "artefact.h"
#include "colour.h"
#include "describe.h"
#include "dungeon.h"
#include "item-name.h"
#include "item-prop.h"
#include "item-status-flag-type.h"
#include "items.h"
#include "libutil.h" // map_find
#include "mpr.h"
#include "randbook.h"
#include "random-pick.h" // talismans
#include "skills.h" // is_removed_skill
#include "spl-book.h"
#include "state.h"
#include "stepdown.h"
#include "stringutil.h"
#include "tag-version.h"

static void _setup_fallback_randart(const int unrand_id,
                                    item_def &item,
                                    int &force_type,
                                    int &item_level);

int create_item_named(string name, coord_def pos, string *error)
{
    trim_string(name);

    item_list ilist;
    // Create the specified item regardless of whether it's excluded.
    const string err = ilist.add_item(name, false, true);
    if (!err.empty())
    {
        if (error)
            *error = err;
        return NON_ITEM;
    }

    item_spec ispec = ilist.get_item(0);
    int item = dgn_place_item(ispec, pos);
    if (item != NON_ITEM)
        link_items();
    else if (error)
        *error = "Failed to create item '" + name + "'";

    return item;
}

bool got_curare_roll(const int item_level)
{
    return one_chance_in(item_level > 27 ? 6   :
                         item_level < 2  ? 15  :
                         (364 - 7 * item_level) / 25);
}

/// A mapping from randomly-described object types to their per-game descript
static map<object_class_type, item_description_type> _type_to_idesc = {
    {OBJ_WANDS, IDESC_WANDS},
    {OBJ_POTIONS, IDESC_POTIONS},
    {OBJ_JEWELLERY, IDESC_RINGS},
    {OBJ_SCROLLS, IDESC_SCROLLS},
    {OBJ_STAVES, IDESC_STAVES},
};

/**
 * Initialize the randomized appearance of a given item.
 *
 * For e.g. wand names, potions colors, helmet tiles...
 *
 * XXX: could this be moved into a constructor or reset method...?
 *
 * @param item  The item to have its appearance initialized.
 */
void item_colour(item_def &item)
{
    // Compute random tile/colour choice.
    item.rnd = 1 + random2(255); // reserve 0 for uninitialized

    // reserve the high bit for marking 1 in 10 books "visually special"
    if (item.base_type == OBJ_BOOKS)
    {
        if (one_chance_in(10))
            item.rnd |= 128;
        else
            item.rnd = 1 + random2(127); // can't just trim the high bit,
                                         // since it might be the only set bit
    }

    if (is_unrandom_artefact(item))
        return; // don't stomp on item.special!

    // initialize item appearance.
    // we don't actually *need* to store this now, but we *do* need to store
    // it in appearance at some point, since sub_type isn't public information
    // for un-id'd items, and therefore can't be used to do a lookup at the
    // time item names/colours are calculated.
    if (auto idesc = map_find(_type_to_idesc, item.base_type))
        item.subtype_rnd = you.item_description[*idesc][item.sub_type];
}

static weapon_type _determine_weapon_subtype(int item_level)
{
    if (one_chance_in(30) && x_chance_in_y(item_level + 3, 100))
    {
        return random_choose(WPN_LAJATANG,
                             WPN_HAND_CANNON,
                             WPN_TRIPLE_CROSSBOW,
                             WPN_DEMON_WHIP,
                             WPN_DEMON_BLADE,
                             WPN_DEMON_TRIDENT,
                             WPN_DOUBLE_SWORD,
                             WPN_EVENINGSTAR,
                             WPN_EXECUTIONERS_AXE,
                             WPN_QUICK_BLADE,
                             WPN_TRIPLE_SWORD);
    }
    else if (x_chance_in_y(item_level + 1, 27))
    {
        // Pick a weapon based on rarity.
        while (true)
        {
            const int wpntype = random2(NUM_WEAPONS);

            if (x_chance_in_y(weapon_rarity(wpntype), 10))
                return static_cast<weapon_type>(wpntype);
        }
    }
    else if (x_chance_in_y(item_level + 1, item_level + 11))
    {
        return random_choose(WPN_QUARTERSTAFF,
                             WPN_FALCHION,
                             WPN_LONG_SWORD,
                             WPN_WAR_AXE,
                             WPN_TRIDENT,
                             WPN_FLAIL,
                             WPN_RAPIER);
    }
    else
    {
        return random_choose(WPN_SLING,
                             WPN_SPEAR,
                             WPN_HAND_AXE,
                             WPN_MACE,
                             // Not worth _weighted for one doubled type.
                             WPN_DAGGER, WPN_DAGGER,
                             WPN_CLUB,
                             WPN_WHIP,
                             WPN_SHORT_SWORD);
    }
}

static bool _try_make_item_unrand(item_def& item, int &force_type, int item_level, int agent)
{
    if (player_in_branch(BRANCH_PANDEMONIUM) && agent == NO_AGENT)
        return false;

    // Can we generate unrands that were lost in the abyss?
    const bool include_abyssed = player_in_branch(BRANCH_ABYSS)
                                 && agent == NO_AGENT;
    const int idx = find_okay_unrandart(item.base_type, force_type, item_level,
                                        include_abyssed);
    if (idx == -1)
        return false;

    // if an idx was found that exists, the unrand was generated via
    // acquirement or similar; replace it with a fallback randart.
    // Choosing a new unrand could break seed stability.
    if (get_unique_item_status(idx) == UNIQ_EXISTS_NONLEVELGEN)
    {
        _setup_fallback_randart(idx, item, force_type, item_level);
        return false;
    }

    return make_item_unrandart(item, idx);
}

static bool _weapon_disallows_randart(int sub_type)
{
    // Clubs are never randarts.
    return sub_type == WPN_CLUB;
}

// Return whether we made an artefact.
static bool _try_make_weapon_artefact(item_def& item, int force_type,
                                      int item_level, bool force_randart,
                                      int agent)
{
    const int old_ego = item.brand;
    if (item_level > 0 && x_chance_in_y(101 + item_level * 3, 4000)
        || force_randart)
    {
        // Make a randart or unrandart.

        // 1 in 20 randarts are unrandarts.
        if (one_chance_in(item_level == ISPEC_GOOD_ITEM ? 7 : 20)
            && !force_randart)
        {
            if (_try_make_item_unrand(item, force_type, item_level, agent))
                return true;
            if (item.base_type == OBJ_STAVES)
            {
                // TODO: this is a bit messy: a fallback randart for an unrand
                // enhancer stave can be generated this way, and this code won't
                // currently respect an explicit request for an enhancer stave
                // as a fallback.
                item.base_type = OBJ_WEAPONS;
                force_type = WPN_QUARTERSTAFF;
            }
            // update sub type for fallback randarts (otherwise, the call will
            // not have changed this value)
            if (force_type != OBJ_RANDOM)
                item.sub_type = force_type;
        }

        if (_weapon_disallows_randart(item.sub_type))
            return false;

        // Mean enchantment +5.
        item.plus = 12 - biased_random2(10,2);
        item.plus -= biased_random2(7,2);
        item.plus -= biased_random2(7,2);

        // On weapons, an enchantment of less than 0 is never viable.
        item.plus = max(static_cast<int>(item.plus), random2(2));

        // The rest are normal randarts.
        if (!make_item_randart(item, force_randart))
            return false;

        // Bane is a worse property than most negative values, so let's boost
        // the resulting item a bit to tempt people into using it.
        if (artefact_property(item, ARTP_BANE))
            item.plus = min(12, item.plus + random_range(2, 5));

        if (old_ego > 0)
            set_artefact_brand(item, old_ego);

        return true;
    }

    return false;
}

/**
 * The number of times to try finding a brand for a given weapon.
 *
 * Result may vary from call to call.
 */
static int _num_brand_tries(const item_def& item, int item_level)
{
    if (item_level >= ISPEC_GIFT)
        return 5;
    if (is_demonic(item)
        || is_blessed(item)
        // Hand crossbows usually appear late, so encourage use.
        || item.sub_type == WPN_HAND_CANNON
        || x_chance_in_y(101 + item_level, 300))
    {
        return 1;
    }
    return 0;
}

brand_type determine_weapon_brand(const item_def& item, int item_level)
{
    // Forced ego.
    if (item.brand != 0)
        return static_cast<brand_type>(item.brand);

    const weapon_type wpn_type = static_cast<weapon_type>(item.sub_type);
    const int tries       = _num_brand_tries(item, item_level);
    brand_type rc         = SPWPN_NORMAL;

    for (int count = 0; count < tries && rc == SPWPN_NORMAL; ++count)
        rc = choose_weapon_brand(wpn_type);

    ASSERT(is_weapon_brand_ok(item.sub_type, rc, true));
    return rc;
}

// Reject brands which are outright bad for the item. Unorthodox combinations
// are ok, since they can happen on randarts.
bool is_weapon_brand_ok(int type, int brand, bool /*strict*/)
{
    item_def item;
    item.base_type = OBJ_WEAPONS;
    item.sub_type = type;

    if (is_blessed_weapon_type(type) && brand != SPWPN_HOLY_WRATH)
        return false;

    if (brand <= SPWPN_NORMAL)
        return true;

    if (type == WPN_QUICK_BLADE && brand == SPWPN_SPEED)
        return false;

    if (is_demonic_weapon_type(type) && brand == SPWPN_HOLY_WRATH)
        return false;

    switch ((brand_type)brand)
    {
    // Universal brands.
    case SPWPN_NORMAL:
    case SPWPN_VENOM:
    case SPWPN_PROTECTION:
    case SPWPN_SPEED:
    case SPWPN_HEAVY:
    case SPWPN_CHAOS:
    case SPWPN_HOLY_WRATH:
    case SPWPN_ELECTROCUTION:
    case SPWPN_FLAMING:
    case SPWPN_FREEZING:
    case SPWPN_DRAINING:
    case SPWPN_ANTIMAGIC:
        break;

    // Melee-only brands.
    case SPWPN_VAMPIRISM:
    case SPWPN_PAIN:
    case SPWPN_DISTORTION:
    case SPWPN_SPECTRAL:
    case SPWPN_REAPING:
    case SPWPN_FOUL_FLAME: // only exists on Pan lords/Brilliance
    case SPWPN_REBUKE:
    case SPWPN_VALOUR:
    case SPWPN_ENTANGLING:
    case SPWPN_SUNDERING:
    case SPWPN_CONCUSSION:
    case SPWPN_DEVIOUS:
        if (is_range_weapon(item))
            return false;
        break;

    // Ranged-only brands.
    case SPWPN_PENETRATION:
    case SPWPN_ACID: // only exists on acidic bite/Punk
        if (!is_range_weapon(item))
            return false;
        break;

#if TAG_MAJOR_VERSION == 34
    // Removed brands.
    case SPWPN_RETURNING:
    case SPWPN_REACHING:
    case SPWPN_ORC_SLAYING:
    case SPWPN_FLAME:
    case SPWPN_FROST:
    case SPWPN_DRAGON_SLAYING:
    case SPWPN_EVASION:
        return false;
#endif

    case SPWPN_CONFUSE:
    case SPWPN_WEAKNESS:
    case SPWPN_VULNERABILITY:
    case SPWPN_FORBID_BRAND:
    case SPWPN_DEBUG_RANDART:
    case NUM_SPECIAL_WEAPONS:
    case NUM_REAL_SPECIAL_WEAPONS:
        die("invalid brand %d on weapon %d (%s)", brand, type,
            item.name(DESC_PLAIN).c_str());
        break;
    }

    return true;
}

// Choose a random weapon type compatible with any supplied brand and fixed
// artprops. If no weapon type is compatible with both of these, use the last
// chosen type, set no brand, and ignore the fixed artprops.
static void _roll_weapon_type(item_def& item, int item_level)
{
    for (int i = 0; i < 1000; ++i)
    {
        item.sub_type = _determine_weapon_subtype(item_level);
        if (is_weapon_brand_ok(item.sub_type, item.brand, true)
            && are_fixed_props_ok(item))
        {
            return;
        }
    }

    item.brand = SPWPN_NORMAL;
}

/// Plusses for a non-artefact weapon with positive plusses.
int determine_nice_weapon_plusses(int item_level)
{
    const int chance = (item_level >= ISPEC_GIFT ? 200 : item_level);

    // Odd-looking, but this is how the algorithm compacts {dlb}.
    int plus = 0;
    for (int i = 0; i < 4; ++i)
    {
        plus += random2(3);

        if (random2(425) > 35 + chance)
            break;
    }

    return plus;
}

void set_artefact_brand(item_def &item, int brand)
{
    item.props[ARTEFACT_PROPS_KEY].get_vector()[ARTP_BRAND].get_short() = brand;
}

static void _generate_weapon_item(item_def& item, bool allow_uniques,
                                  int force_type, int item_level,
                                  int agent = NO_AGENT)
{
    // Determine weapon type.
    if (force_type != OBJ_RANDOM)
        item.sub_type = force_type;
    else
        _roll_weapon_type(item, item_level);

    // Fall back to an ordinary item if randarts not allowed for this type.
    if (item_level == ISPEC_RANDART && _weapon_disallows_randart(item.sub_type))
        item_level = ISPEC_GOOD_ITEM;

    // Forced randart.
    if (item_level == ISPEC_RANDART)
    {
        int ego = item.brand;
        for (int i = 0; i < 100; ++i)
            if (_try_make_weapon_artefact(item, force_type, 0, true, agent))
            {
                if (ego > SPWPN_NORMAL)
                    set_artefact_brand(item, ego);

                if (randart_is_bad(item)) // recheck, the brand changed
                {
                    force_type = item.sub_type;
                    item.clear();
                    item.quantity = 1;
                    item.base_type = OBJ_WEAPONS;
                    item.sub_type = force_type;
                    continue;
                }
                return;
            }
        // fall back to an ordinary item
        item_level = ISPEC_GOOD_ITEM;
    }

    // If we make the unique roll, no further generation necessary.
    if (allow_uniques
        && _try_make_weapon_artefact(item, force_type, item_level, false, agent))
    {
        return;
    }

    ASSERT(!is_artefact(item));

    // Artefacts handled, let's make a normal item.
    const bool force_good = item_level >= ISPEC_GIFT;
    const bool forced_ego = item.brand > 0;
    const bool no_brand   = item.brand == SPWPN_FORBID_BRAND;

    if (no_brand)
        set_item_ego_type(item, OBJ_WEAPONS, SPWPN_NORMAL);

    // If it's forced to be a good item, reroll clubs.
    while (force_good && force_type == OBJ_RANDOM && item.sub_type == WPN_CLUB)
        _roll_weapon_type(item, item_level);

    item.plus = 0;

    if (item_level < 0)
    {
        // Thoroughly damaged, could had been good once.
        if (!no_brand && (forced_ego || one_chance_in(4)))
        {
            // Brand is set as for "good" items.
            set_item_ego_type(item, OBJ_WEAPONS,
                determine_weapon_brand(item, 2 + 2 * env.absdepth0));
        }
        item.plus -= 1 + random2(3);
    }
    else if ((force_good || is_demonic(item)
              || item.sub_type == WPN_HAND_CANNON || forced_ego
                    || x_chance_in_y(51 + item_level, 200))
                && (!item.is_mundane() || force_good))
    {
        // Make a better item (possibly ego).
        if (!no_brand)
        {
            set_item_ego_type(item, OBJ_WEAPONS,
                              determine_weapon_brand(item, item_level));
        }

        // if acquired item still not ego... enchant it up a bit.
        if (force_good && item.brand == SPWPN_NORMAL)
            item.plus += 2 + random2(3);

        item.plus += determine_nice_weapon_plusses(item_level);

        // squash boring items.
        if (!force_good && item.brand == SPWPN_NORMAL && item.plus < 3)
            item.plus = 0;
    }

    // Blessed weapons must always be branded with holy wrath.
    if (is_blessed(item))
    {
        set_item_ego_type(item, OBJ_WEAPONS,
                          determine_weapon_brand(item, item_level));
    }
}

// Remember to update the code in is_missile_brand_ok if adding or altering
// brands that are applied to missiles. {due}
static special_missile_type _determine_missile_brand(const item_def& item,
                                                     int item_level)
{
    // Forced ego.
    if (item.brand != 0)
        return static_cast<special_missile_type>(item.brand);

    special_missile_type rc = SPMSL_NORMAL;

    // Weight of SPMSL_NORMAL
    // Gifts from Trog/Oka can be unbranded boomerangs/javelins
    // but not poisoned darts
    int nw = item_level >= ISPEC_GOOD_ITEM ?   0 :
             item_level == ISPEC_GIFT      ? 120
                                           : random2(2000 - 55 * item_level);

    // Weight of SPMSL_POISONED
    int pw = item_level >= ISPEC_GIFT ? 0 : random2(2000 - 55 * item_level);

    switch (item.sub_type)
    {
    case MI_THROWING_NET:
    case MI_STONE:
    case MI_LARGE_ROCK:
        rc = SPMSL_NORMAL;
        break;
    case MI_BOOMERANG:
        rc = SPMSL_NORMAL;
        break;
    case MI_DART:
        // Curare is special cased, all the others aren't.
        if (got_curare_roll(item_level))
        {
            rc = SPMSL_CURARE;
            break;
        }

        rc = random_choose_weighted(60, SPMSL_BLINDING,
                                    20, SPMSL_FRENZY,
                                    20, SPMSL_DISJUNCTION,
                                    pw, SPMSL_POISONED);
        break;
    case MI_JAVELIN:
        rc = random_choose_weighted(90, SPMSL_SILVER,
                                    nw, SPMSL_NORMAL);
        break;
    }

    ASSERT(is_missile_brand_ok(item.sub_type, rc, true));

    return rc;
}

bool is_missile_brand_ok(int type, int brand, bool strict)
{
    // Rocks can't normally be branded.
    if ((type == MI_STONE || type == MI_LARGE_ROCK)
        && brand != SPMSL_NORMAL
        && strict)
    {
        return false;
    }

    // Never generates, only used for chaos-branded missiles.
    if (brand == SPMSL_FLAME || brand == SPMSL_FROST)
        return false;

    // In contrast, darts should always be branded.
    // And all of these brands save poison are unique to darts.
    switch (brand)
    {
    case SPMSL_POISONED:
        if (type == MI_DART)
            return true;
        break;

    case SPMSL_CURARE:
#if TAG_MAJOR_VERSION == 34
    case SPMSL_PARALYSIS:
#endif
    case SPMSL_FRENZY:
    case SPMSL_DISPERSAL:
    case SPMSL_DISJUNCTION:
        return type == MI_DART;

    case SPMSL_BLINDING:
        // possible on ex-pies
        return type == MI_DART || (type == MI_BOOMERANG && !strict);

    default:
        if (type == MI_DART)
            return false;
    }

    // Everything else doesn't matter.
    if (brand == SPMSL_NORMAL)
        return true;

    // In non-strict mode, everything other than darts is mostly ok.
    if (!strict)
        return true;

    // Not a missile?
    if (type == 0)
        return true;

    // Specifics
    switch (brand)
    {
    case SPMSL_POISONED:
        return false;
    case SPMSL_CHAOS:
        return type == MI_BOOMERANG || type == MI_JAVELIN;
    case SPMSL_SILVER:
        return type == MI_JAVELIN;
    default: break;
    }

    // Assume no, if we've gotten this far.
    return false;
}

static void _generate_missile_item(item_def& item, int force_type,
                                   int item_level)
{
    const bool no_brand = (item.brand == SPMSL_FORBID_BRAND);
    if (no_brand)
        item.brand = SPMSL_NORMAL;

    item.plus = 0;

    if (force_type != OBJ_RANDOM)
        item.sub_type = force_type;
    else
    {
        // Total weight: 100
        item.sub_type =
            random_choose_weighted(60, MI_DART,
                                   17, MI_BOOMERANG,
                                   11,  MI_JAVELIN,
                                   6,  MI_THROWING_NET,
                                   6,  MI_LARGE_ROCK);
    }

    // No fancy rocks -- break out before we get to special stuff.
    if (item.sub_type == MI_LARGE_ROCK)
    {
        item.quantity = 2 + random2avg(5,2);
        return;
    }
    else if (item.sub_type == MI_STONE)
    {
        item.quantity = roll_dice(2, 5) - 1;
        return;
    }
    else if (item.sub_type == MI_THROWING_NET) // no fancy nets, either
    {
        item.quantity = 1 + one_chance_in(4); // and only one, rarely two
        return;
    }

    if (!no_brand)
    {
        set_item_ego_type(item, OBJ_MISSILES,
                           _determine_missile_brand(item, item_level));
    }

    item.quantity = random_range(2, 6);
}

// Increment a given artprop on a given item while ensuring it doesn't overflow
// sensible values.
static void _increment_artprop(item_def& item, artefact_prop_type prop, int value)
{
    ASSERT(value > 0);

    short& val = item.props[ARTEFACT_PROPS_KEY].get_vector()[prop].get_short();
    val += value;

    if (artp_value_type(prop) == ARTP_VAL_BOOL)
        val = min((short)1, val);
    else
    {
        switch (prop)
        {
            case ARTP_FIRE:
            case ARTP_COLD:
            case ARTP_WILLPOWER:
            case ARTP_NEGATIVE_ENERGY:
                val = min((short)3, val);
                break;

            default:
                break;
        }
    }
}

static bool _try_make_armour_artefact(item_def& item, int force_type,
                                      int item_level, int agent)
{
    const bool force_randart = item_level == ISPEC_RANDART;
    const int old_ego = item.brand;
    if (!force_randart && (item_level <= 0
                           || !x_chance_in_y(101 + item_level * 3, 4000)))
    {
        return false;
    }
    // Make a randart or unrandart.

    // 1 in 20 randarts are unrandarts.
    if (one_chance_in(item_level == ISPEC_GOOD_ITEM ? 7 : 20)
        && !force_randart)
    {
        if (_try_make_item_unrand(item, force_type, item_level, agent))
            return true;
    }

    // The rest are normal randarts.

    // 5% of boots become barding.
    if (item.sub_type == ARM_BOOTS && one_chance_in(20))
        item.sub_type = ARM_BARDING;

    // Determine enchantment.
    if (!armour_is_enchantable(item) || one_chance_in(5))
        item.plus = 0;
    else
    {
        int max_plus = armour_max_enchant(item);
        item.plus = random2(max_plus + 1);

        if (one_chance_in(5))
            item.plus += random2(max_plus + 6) / 2;

        if (one_chance_in(6))
            item.plus -= random2(max_plus + 6);

        // On body armour, an enchantment of less than 0 is never viable.
        // On aux armour & shields, going below -2 is likewise unviable.
        // (You think you're better than the hat of the Alchemist?)
        if (get_armour_slot(item) == SLOT_BODY_ARMOUR)
            item.plus = max(static_cast<int>(item.plus), random2(2));
        else
            item.plus = max(static_cast<int>(item.plus), random_range(-2, 1));
    }

    // Needs to be done after the barding chance else we get randart
    // bardings named Boots of xy.
    if (!make_item_randart(item, force_randart))
        return false;

    // Bane is a worse property than most negative values, so let's make them a
    // bit more tempting on average.
    if (is_artefact(item) && artefact_property(item, ARTP_BANE) && item.plus < armour_max_enchant(item) * 3 / 2)
        item.plus += random_range(1, 3);

    // Having an ego before this function means that it was specifically requested
    // by itemspec, so we should try to honour that.
    if (old_ego > 0)
    {
        artefact_prop_type prop = ego_to_artprop(static_cast<special_armour_type>(old_ego));

        // This requires special handling since it maps to a *pair* of artprops at once.
        if (old_ego == SPARM_RESISTANCE)
        {
            _increment_artprop(item, ARTP_FIRE, 1);
            _increment_artprop(item, ARTP_COLD, 1);
        }
        // Egos that have no corresponding artprop can stay intact
        else if (prop == ARTP_NUM_PROPERTIES)
            set_artefact_brand(item, old_ego);
        else
        {
            // Other egos are directly translated into the corresponding artprop,
            // to make inscriptions a bit less confusing for players. (eg: no {rF+, rF+})
            switch (prop)
            {
                case ARTP_STRENGTH:
                case ARTP_INTELLIGENCE:
                case ARTP_DEXTERITY:
                case ARTP_AC:
                    _increment_artprop(item, prop, 3);
                    break;

                default:
                    _increment_artprop(item, prop, 1);
            }
        }
    }

    return true;
}

/**
 * Generate an appropriate ego for a piece of armour.
 *
 * @param item          The item in question.
 * @return              The item's current ego, if it already has one;
 *                      otherwise, an ego appropriate to the item.
 *                      May be SPARM_NORMAL.
 */
static special_armour_type _generate_armour_ego(const item_def& item)
{
    if (item.brand != SPARM_NORMAL)
        return static_cast<special_armour_type>(item.brand);

    const auto e = choose_armour_ego(static_cast<armour_type>(item.sub_type));
    ASSERT(is_armour_brand_ok(item.sub_type, e, true));
    return e;
}

bool is_armour_brand_ok(int type, int brand, bool strict)
{
    equipment_slot slot = get_armour_slot((armour_type)type);

    // Currently being too restrictive results in asserts, being too
    // permissive will generate such items on "any armour ego:XXX".
    // The latter is definitely so much safer -- 1KB
    switch ((special_armour_type)brand)
    {
    case SPARM_FORBID_EGO:
    case SPARM_NORMAL:
        return true;

    case SPARM_FLYING:
        if (slot == SLOT_BODY_ARMOUR)
            return true;
        // deliberate fall-through
#if TAG_MAJOR_VERSION == 34
    case SPARM_RUNNING:
    case SPARM_JUMPING:
#endif
    case SPARM_RAMPAGING:
    case SPARM_EARTH:
        return slot == SLOT_BOOTS || slot == SLOT_BARDING;
    case SPARM_STEALTH:
        return slot == SLOT_BOOTS || slot == SLOT_BARDING || slot == SLOT_CLOAK
            || slot == SLOT_HELMET || slot == SLOT_GLOVES;

    case SPARM_ARCHMAGI:
        return !strict || type == ARM_ROBE;

    case SPARM_PONDEROUSNESS:
        return true;
    case SPARM_CORROSION_RESISTANCE:
    case SPARM_AIR:
#if TAG_MAJOR_VERSION > 34
        return slot == SLOT_CLOAK || slot == SLOT_OFFHAND;
#endif
#if TAG_MAJOR_VERSION == 34
        if (type == ARM_PLATE_ARMOUR && !strict)
            return true;
        return slot == SLOT_CLOAK || slot == SLOT_OFFHAND;
    case SPARM_INVISIBILITY:
        return (slot == SLOT_CLOAK && !strict) || type == ARM_SCARF;
#endif

    case SPARM_REFLECTION:
    case SPARM_PROTECTION:
        return slot == SLOT_OFFHAND;

    case SPARM_STRENGTH:
    case SPARM_DEXTERITY:
    case SPARM_INFUSION:
        if (!strict)
            return true;
        // deliberate fall-through
    case SPARM_HURLING:
    case SPARM_FIRE:
    case SPARM_PARRYING:
        return slot == SLOT_GLOVES;

    case SPARM_SEE_INVISIBLE:
        return type == ARM_HAT;
    case SPARM_INTELLIGENCE:
    case SPARM_SNIPING:
    case SPARM_ICE:
        return slot == SLOT_HELMET;

    case SPARM_FIRE_RESISTANCE:
    case SPARM_COLD_RESISTANCE:
        if (type == ARM_SCARF)
            return false;
        // deliberate fall-through
    case SPARM_RESISTANCE:
        if (type == ARM_FIRE_DRAGON_ARMOUR || type == ARM_ICE_DRAGON_ARMOUR
            || type == ARM_GOLDEN_DRAGON_ARMOUR || type == ARM_ORB)
        {
            return false; // contradictory, redundant, or overriding other bits
        }
        return true; // in portal vaults, these can happen on every slot

    case SPARM_WILLPOWER:
        if (type == ARM_HAT)
            return true;
        // deliberate fall-through
    case SPARM_POISON_RESISTANCE:
    case SPARM_POSITIVE_ENERGY:
        if (type == ARM_PEARL_DRAGON_ARMOUR && brand == SPARM_POSITIVE_ENERGY)
            return false; // contradictory or redundant

        return slot == SLOT_BODY_ARMOUR || slot == SLOT_OFFHAND && type != ARM_ORB
                       || slot == SLOT_CLOAK && type != ARM_SCARF || !strict;

    case SPARM_SPIRIT_SHIELD:
        return
#if TAG_MAJOR_VERSION == 34
               type == ARM_HAT ||
               type == ARM_CAP ||
               type == ARM_SCARF ||
#endif
               slot == SLOT_OFFHAND || !strict;

    case SPARM_REPULSION:
    case SPARM_HARM:
#if TAG_MAJOR_VERSION > 34
    case SPARM_INVISIBILITY:
#endif
#if TAG_MAJOR_VERSION == 34
    case SPARM_CLOUD_IMMUNE:
#endif
    case SPARM_SHADOWS:
        return type == ARM_SCARF;

    case SPARM_LIGHT:
        return type == ARM_ORB || type == ARM_HELMET;
    case SPARM_RAGE:
    case SPARM_MAYHEM:
    case SPARM_GUILE:
    case SPARM_ENERGY:
    case SPARM_GLASS:
    case SPARM_PYROMANIA:
    case SPARM_STARDUST:
    case SPARM_MESMERISM:
    case SPARM_ATTUNEMENT:
        return type == ARM_ORB;

    case SPARM_ARCHERY:
        return slot == SLOT_BODY_ARMOUR && type != ARM_ROBE;

    case SPARM_COMMAND:
    case SPARM_DEATH:
    case SPARM_RESONANCE:
        return slot == SLOT_BODY_ARMOUR;

    case NUM_SPECIAL_ARMOURS:
    case NUM_REAL_SPECIAL_ARMOURS:
        die("invalid armour brand");
    }


    return true;
}

/**
 * Return the number of plusses required for a type of armour to be notable.
 * (From plus alone.)
 *
 * @param armour_type   The type of armour being considered.
 * @return              The armour plus value required to be interesting.
 */
static int _armour_plus_threshold(equipment_slot armour_type)
{
    switch (armour_type)
    {
        // body armour is very common; squelch most of it
        case SLOT_BODY_ARMOUR:
            return 3;
        // shields are fairly common
        case SLOT_OFFHAND:
            return 2;
        // aux armour is relatively uncommon
        default:
            return 1;
    }
}

armour_type pick_random_aux_armour_type()
{
    return random_choose_weighted(12, ARM_BOOTS,
                                  12, ARM_GLOVES,
                                  // Cloak slot
                                  9, ARM_CLOAK,
                                  3, ARM_SCARF,
                                  // Head slot
                                  10, ARM_HELMET,
                                  2, ARM_HAT);
}

armour_type pick_random_shield_type()
{
    return random_choose_weighted(4, ARM_BUCKLER,
                                  2, ARM_KITE_SHIELD,
                                  1, ARM_TOWER_SHIELD);
}

armour_type pick_random_body_armour_type(int item_level)
{
    if (x_chance_in_y(11 + item_level, 10000))
    {
        // High level dragon scales
        return random_choose(ARM_STEAM_DRAGON_ARMOUR,
                             ARM_ACID_DRAGON_ARMOUR,
                             ARM_STORM_DRAGON_ARMOUR,
                             ARM_GOLDEN_DRAGON_ARMOUR,
                             ARM_SWAMP_DRAGON_ARMOUR,
                             ARM_PEARL_DRAGON_ARMOUR,
                             ARM_SHADOW_DRAGON_ARMOUR,
                             ARM_QUICKSILVER_DRAGON_ARMOUR);
    }
    else if (x_chance_in_y(11 + item_level, 8000))
    {
        // Crystal plate, some armours which are normally gained by butchering
        // monsters for hides.
        return random_choose(ARM_CRYSTAL_PLATE_ARMOUR,
                             ARM_TROLL_LEATHER_ARMOUR,
                             ARM_FIRE_DRAGON_ARMOUR,
                             ARM_ICE_DRAGON_ARMOUR);

    }
    else if (x_chance_in_y(11 + item_level, 60))
    {
        // All the "mundane" armours. Generally the player will find at least
        // one copy of these by the Lair.
        return random_choose(ARM_ROBE,
                             ARM_LEATHER_ARMOUR,
                             ARM_RING_MAIL,
                             ARM_SCALE_MAIL,
                             ARM_CHAIN_MAIL,
                             ARM_PLATE_ARMOUR);
    }
    else if (x_chance_in_y(11 + item_level, 35))
    {
        // All the "mundane" amours except plate.
        return random_choose(ARM_ROBE,
                             ARM_LEATHER_ARMOUR,
                             ARM_RING_MAIL,
                             ARM_SCALE_MAIL,
                             ARM_CHAIN_MAIL);
    }
    else
    {
        // Default (lowest-level) armours.
        return random_choose(ARM_ROBE,
                             ARM_LEATHER_ARMOUR,
                             ARM_RING_MAIL);
    }
}

/**
 * Pick an armour type (ex. plate armour), based on item_level
 *
 * @param item_level The rough power level of the item.
 *
 * @return The selected armour type.
 */
static armour_type _get_random_armour_type(int item_level)
{
    // Dummy value for initialization, always changed by the conditional
    // (and not changing it would trigger an ASSERT)
    armour_type armtype = NUM_ARMOURS;

    // Secondary armours.
    if (one_chance_in(5))
    {
        if (x_chance_in_y(48, 60))
            armtype = pick_random_aux_armour_type();
        else if (x_chance_in_y(7, 11))
            armtype = pick_random_shield_type();
        else
            armtype = ARM_ORB;
    }
    else
        armtype = pick_random_body_armour_type(item_level);

    // XXX: Taking the weight out of arguably the least valuable armour types.
    // Makes orbs a bit more common earlier in the game (potentially before a
    // player has invested in shields).
    if ((armtype == ARM_ROBE || armtype == ARM_LEATHER_ARMOUR) && one_chance_in(25))
        armtype = ARM_ORB;

    ASSERT(armtype != NUM_ARMOURS);

    return armtype;
}

static void _generate_armour_item(item_def& item, bool allow_uniques,
                                  int force_type, int item_level,
                                  int agent = NO_AGENT)
{
    if (force_type != OBJ_RANDOM)
        item.sub_type = force_type;
    else
    {
        for (int i = 0; i < 1000; ++i)
        {
            item.sub_type = _get_random_armour_type(item_level);
            if (is_armour_brand_ok(item.sub_type, item.brand, true)
                && are_fixed_props_ok(item))
            {
                break;
            }
        }
    }

    // Forced randart.
    if (item_level == ISPEC_RANDART)
    {
        for (int i = 0; i < 100; ++i)
            if (_try_make_armour_artefact(item, force_type, item_level, agent))
            {
                if (randart_is_bad(item)) // recheck, the brand changed
                {
                    force_type = item.sub_type;
                    item.clear();
                    item.quantity = 1;
                    item.base_type = OBJ_ARMOUR;
                    item.sub_type = force_type;
                    continue;
                }
                return;
            }
        // fall back to an ordinary item
        item_level = ISPEC_GOOD_ITEM;
    }

    if (allow_uniques
        && _try_make_armour_artefact(item, force_type, item_level, agent))
    {
        return;
    }

    if (item.sub_type == ARM_BOOTS && one_chance_in(8))
        item.sub_type = ARM_BARDING;

    const bool force_good = item_level >= ISPEC_GIFT;
    const bool forced_ego = (item.brand > 0);
    const bool no_ego     = (item.brand == SPARM_FORBID_EGO);

    if (item_always_has_ego(item))
        set_item_ego_type(item, OBJ_ARMOUR, _generate_armour_ego(item));
    else if (no_ego)
        item.brand = SPARM_NORMAL;
    // An additional chance for egos on aux armour (without giving more plusses)
    else if (armour_is_aux(static_cast<armour_type>(item.sub_type))
             && x_chance_in_y(item_level * 3 + 15, 200))
    {
        set_item_ego_type(item, OBJ_ARMOUR, _generate_armour_ego(item));
    }

    if (item_level < 0)
    {
        // Thoroughly damaged, could have been good once.
        if (!no_ego && (forced_ego || one_chance_in(4)))
        {
            // Brand is set as for "good" items.
            set_item_ego_type(item, OBJ_ARMOUR, _generate_armour_ego(item));
        }

        item.plus -= 1 + random2(3);
    }
    else if ((forced_ego || item.sub_type == ARM_HAT
                    || x_chance_in_y(51 + item_level, 250))
                && !item.is_mundane() || force_good)
    {
        // Make a good item...
        item.plus += random2(3);

        if (item.sub_type <= ARM_PLATE_ARMOUR
            && x_chance_in_y(21 + item_level, 300))
        {
            item.plus += random2(3);
        }

        if (!no_ego && x_chance_in_y(31 + item_level, 350))
        {
            // ...an ego item, in fact.
            set_item_ego_type(item, OBJ_ARMOUR, _generate_armour_ego(item));

            if (get_armour_ego_type(item) == SPARM_PONDEROUSNESS)
                item.plus += 3 + random2(8);
        }
    }

    // Don't overenchant items.
    if (item.plus > armour_max_enchant(item))
        item.plus = armour_max_enchant(item);

    // Don't let unenchantable armour get minuses.
    if (!armour_is_enchantable(item))
        item.plus = 0;

    // Never give brands to scales or hides, in case of misbehaving vaults.
    if (armour_type_is_hide(static_cast<armour_type>(item.sub_type)))
        set_item_ego_type(item, OBJ_ARMOUR, SPARM_NORMAL);

    // squash boring items.
    if (!force_good && item.brand == SPARM_NORMAL && item.plus > 0
        && item.plus < _armour_plus_threshold(get_armour_slot(item)))
    {
        item.plus = 0;
    }
}

/**
 * Choose a random wand subtype for ordinary wand generation.
 *
 * Some wands [mostly more powerful ones] are less common than others.
 * Attack wands are more common, mostly to preserve historical wand freqs.
 *
 * @return      A random wand_type.
 */
static int _random_wand_subtype()
{
    const auto hex_wand_type = (wand_type)item_for_set(ITEM_SET_HEX_WANDS);
    const auto beam_wand_type = (wand_type)item_for_set(ITEM_SET_BEAM_WANDS);
    const auto blast_wand_type = (wand_type)item_for_set(ITEM_SET_BLAST_WANDS);
    // total weight 70 [arbitrary]
    return random_choose_weighted(14, WAND_FLAME,
                                  14, blast_wand_type,
                                  14, hex_wand_type,
                                  9, beam_wand_type,
                                  7, WAND_POLYMORPH,
                                  7, WAND_MINDBURST,
                                  5, WAND_DIGGING);
}

/**
 * Should wands of this type NOT spawn extremely early on? (At very low
 * item_level, or in the hands of very low HD monsters?)
 *
 * @param type      The wand_type in question.
 * @return          Whether it'd be excessively mean for this wand to be used
 *                  against very early players.
 */
bool is_high_tier_wand(int type)
{
    switch (type)
    {
    case WAND_CHARMING:
    case WAND_PARALYSIS:
    case WAND_ACID:
    case WAND_LIGHT:
    case WAND_QUICKSILVER:
    case WAND_ICEBLAST:
    case WAND_ROOTS:
    case WAND_MINDBURST:
        return true;
    default:
        return false;
    }
}

void generate_wand_item(item_def& item, int force_type, int item_level)
{
    if (force_type != OBJ_RANDOM)
        item.sub_type = force_type;
    else
        item.sub_type = _random_wand_subtype();

    // Add wand charges and ensure we have at least one charge.
    const int max_charges = wand_charge_value(item.sub_type, item_level);
    item.charges = 1 + random2avg(max_charges, 2);

    // Don't let monsters pickup early high-tier wands
    if (item_level < 2 && is_high_tier_wand(item.sub_type))
        item.flags |= ISFLAG_NO_PICKUP;
}

static int _potion_weight(item_rarity_type rarity)
{
    switch (rarity)
    {
    case RARITY_VERY_COMMON:
        return 192;
    case RARITY_COMMON:
        return 105;
    case RARITY_UNCOMMON:
        return 67;
    case RARITY_RARE:
        return 35;
    case RARITY_VERY_RARE:
        return 2;
    default:
        return 0;
    }
}

static void _generate_potion_item(item_def& item, int force_type,
                                  int item_level)
{
    item.quantity = 1;

    if (one_chance_in(18))
        item.quantity++;

    if (one_chance_in(25))
        item.quantity++;

    if (force_type != OBJ_RANDOM)
        item.sub_type = force_type;
    else
    {
        vector<pair<potion_type, int>> weights;
        for (int i = 0; i < NUM_POTIONS; ++i)
        {
            const potion_type pot = (potion_type) i;
            const int weight = _potion_weight(consumable_rarity(OBJ_POTIONS,
                                                                pot));
            if (weight)
            {
                const pair<potion_type, int> weight_pair = { pot, weight };
                weights.push_back(weight_pair);
            }
        }

        // total weight: 1045
        item.sub_type = *random_choose_weighted(weights);
    }
    // don't let monsters pickup early dangerous potions
    if (item_level < 2 && item.sub_type == POT_BERSERK_RAGE)
        item.flags |= ISFLAG_NO_PICKUP;
}

static int _scroll_weight(item_rarity_type rarity)
{
    switch (rarity)
    {
    case RARITY_VERY_COMMON:
        return 200;
    case RARITY_COMMON:
        return 100;
    case RARITY_UNCOMMON:
        return 36;
    case RARITY_RARE:
        return 15;
    case RARITY_VERY_RARE:
        return 9;
    default:
        return 0;
    }
}

static void _generate_scroll_item(item_def& item, int force_type, int agent)
{
    // determine sub_type:
    if (force_type != OBJ_RANDOM)
        item.sub_type = force_type;
    else
    {
        vector<pair<scroll_type, int>> weights;
        for (int i = 0; i < NUM_SCROLLS; ++i)
        {
            const scroll_type scr = (scroll_type) i;

            // Only generate the scroll types chosen for this game.
            if (item_excluded_from_set(OBJ_SCROLLS, scr))
                continue;

            // No teleportation or noise in Sprint.
            if (crawl_state.game_is_sprint()
                && (scr == SCR_TELEPORTATION || scr == SCR_NOISE))
            {
                continue;
            }

            // These scrolls increase knowledge, so Xom considers them boring.
            if (agent == GOD_XOM
                && (scr == SCR_IDENTIFY || scr == SCR_REVELATION))
            {
                continue;
            }

            const int weight = _scroll_weight(consumable_rarity(OBJ_SCROLLS,
                                                                scr));
            if (weight)
            {
                const pair<scroll_type, int> weight_pair = { scr, weight };
                weights.push_back(weight_pair);
            }
        }

        // total weight:    750
        //                 -122  in sprint
        item.sub_type = *random_choose_weighted(weights);
    }

    item.quantity = random_choose_weighted(46, 1,
                                            1, 2,
                                            1, 3);

    item.plus = 0;
}

/// Choose a random skill for a manual to be generated for.
static skill_type _choose_manual_skill()
{
    // spell skill (or invo/evo)
    if (one_chance_in(4))
    {
        skill_type skill;
        do
        {
            skill = static_cast<skill_type>(
                SK_SPELLCASTING + random2(NUM_SKILLS - SK_SPELLCASTING));
        } while (is_removed_skill(skill));
        return skill;
    }

    // mundane skill
    skill_type skill;
    do
    {
        skill = static_cast<skill_type>(random2(SK_LAST_MUNDANE+1));
    } while (is_removed_skill(skill));
    return skill;
}

// Largely used to boost the drop rate of low-level 'utility' spells that are
// of interest all game long (when low-level spells in general get much less
// common as one gets deeper in the game).
static int _spell_base_weight(spell_type spell)
{
    switch (spell)
    {
    case SPELL_APPORTATION:
    case SPELL_BECKONING:
        return 120;

    // Due to spellbook weights, which is actually less common than most level
    // 2 spells by base, so weight it a little higher.
    case SPELL_BLINK:
        return 170;

    case SPELL_SUBLIMATION_OF_BLOOD:
        return 150;

    // Less boost is needed because of their higher level
    case SPELL_OZOCUBUS_ARMOUR:
    case SPELL_PASSWALL:
    case SPELL_SWIFTNESS:
        return 120;

    default:
        return 100;
    }
}

spell_type choose_parchment_spell(int item_level, spschool school,
                                  int fixed_spell_level)
{
    // Keep ISPEC_GOOD_ITEM from being absurdly overweighted to the top end.
    item_level = min(30, item_level);

    // Calculate spell level weights based on item level.
    int lv_weight[9];
    for (int i = 0; i < 9; ++i)
        lv_weight[i] = 100 - min(90, (int)(floor(pow(abs(i - item_level / 5) * 2, 2)) + (i * 5)));

    vector<pair<spell_type, int>> weights;
    for (int i = 0; i < NUM_SPELLS; ++i)
    {
        const spell_type spell = (spell_type) i;

        if (!is_player_book_spell(spell)
            || school != spschool::none && !spell_typematch(spell, school)
            || fixed_spell_level > 0 && spell_difficulty(spell) != fixed_spell_level)
        {
            continue;
        }

        const int splevel = spell_difficulty(spell);
        const int weight = _spell_base_weight(spell) * lv_weight[splevel-1];
        const pair <spell_type, int> weight_pair = { spell, weight };
        weights.push_back(weight_pair);
    }

    // If somehow we have been given criteria that match no spell at all, it's
    // better to return some spell instead of SPELL_NO_SPELL.
    if (weights.empty())
        return SPELL_APPORTATION;

    return *random_choose_weighted(weights);
}

static void _generate_book_item(item_def& item, int force_type, int item_level)
{
    if (force_type != OBJ_RANDOM)
        item.sub_type = force_type;
    else if (x_chance_in_y(21 + item_level, 4200))
        item.sub_type = BOOK_MANUAL; // skill manual - rare!
    else if (!one_chance_in(20))
        item.sub_type = BOOK_PARCHMENT; // almost everything else is a parchment
    else
        item.sub_type = choose_book_type(item_level);

    if (item.sub_type == BOOK_MANUAL)
    {
        item.skill = _choose_manual_skill();
        // Set number of bonus skill points.
        item.skill_points = random_range(2000, 3000);
    }
    else if (item.sub_type == BOOK_PARCHMENT)
        item.plus = static_cast<int>(choose_parchment_spell(item_level));
    else if (item.sub_type == BOOK_RANDART_THEME)
        build_themed_book(item, capped_spell_filter(20));
    else if (item.sub_type == BOOK_RANDART_LEVEL)
    {
        int max_level  = min(9, max(1, item_level / 3));
        int spl_level  = random_range(1, max_level);
        make_book_level_randart(item, spl_level);
    }
}

static stave_type _get_random_stave_type()
{
    static vector<stave_type> valid_types;
    if (valid_types.empty())
        for (int i = STAFF_FIRST_STAFF; i < NUM_STAVES; i++)
            if (!item_type_removed(OBJ_STAVES, (stave_type)i))
                valid_types.push_back((stave_type)i);

    return *random_iterator(valid_types);
}

// Choose a random stave type compatible with any fixed artprops. If no stave
// type is compatible, we use the last chosen type and ignore the fixed
// artprops.
static void _roll_stave_type(item_def& item)
{
    for (int i = 0; i < 1000; ++i)
    {
        item.sub_type = _get_random_stave_type();
        if (are_fixed_props_ok(item))
            return;
    }
}

static void _try_make_staff_artefact(item_def& item, bool allow_uniques,
                                     int item_level, int agent)
{
    const bool force_randart = item_level == ISPEC_RANDART;

    if (allow_uniques
        && !force_randart
        && one_chance_in(item_level == ISPEC_GOOD_ITEM ? 27 : 100))
    {
        // Temporarily fix the base_type to get enhancer staves
        // TODO: ???
        item.base_type = OBJ_WEAPONS;
        int fake_force_type = WPN_STAFF;
        if (_try_make_item_unrand(item, fake_force_type, item_level, agent))
            return;
        // We failed. Go back to trying a staff.
        // TODO: support hypothetical fallback to a specific staff type
        item.base_type = OBJ_STAVES;
    }

    if (force_randart
        // These odds are taken uncritically from _try_make_weapon_artifact.
        // We should probably revisit them.
        || item_level > 0 && x_chance_in_y(101 + item_level * 3, 8000))
    {
        make_item_randart(item);
    }
}

static void _generate_staff_item(item_def& item, bool allow_uniques,
                                 int force_type, int item_level, int agent)
{
    if (force_type == OBJ_RANDOM)
        _roll_stave_type(item);
    else
        item.sub_type = force_type;

    _try_make_staff_artefact(item, allow_uniques, item_level, agent);
}

static void _generate_rune_item(item_def& item, int force_type)
{
    if (force_type == OBJ_RANDOM)
    {
        vector<int> possibles;
        for (int i = 0; i < NUM_RUNE_TYPES; i++)
            if (!item_type_removed(OBJ_RUNES, i) && !you.runes[i]
                && i != RUNE_ELF && i != RUNE_FOREST)
            {
                possibles.push_back(i);
            }

        item.sub_type = possibles.empty()
                      ? RUNE_DEMONIC
                      : *random_iterator(possibles);
    }
    else
        item.sub_type = force_type;
}

static void _generate_gem_item(item_def& item, int force_type)
{
    ASSERT_RANGE(force_type, 0, NUM_GEM_TYPES);
    item.sub_type = force_type;
}

jewellery_type get_random_amulet_type()
{
    static vector<jewellery_type> valid_types;
    if (valid_types.empty())
        for (int i = AMU_FIRST_AMULET; i < NUM_JEWELLERY; i++)
            if (!item_type_removed(OBJ_JEWELLERY, (jewellery_type)i))
                valid_types.push_back((jewellery_type)i);
    return *random_iterator(valid_types);
}

jewellery_type get_random_ring_type()
{
    static vector<jewellery_type> valid_types;
    if (valid_types.empty())
        for (int i = RING_FIRST_RING; i < NUM_RINGS; i++)
            if (!item_type_removed(OBJ_JEWELLERY, (jewellery_type)i))
                valid_types.push_back((jewellery_type)i);
    return *random_iterator(valid_types);
}

static bool _try_make_jewellery_unrandart(item_def& item, int force_type,
                                          int item_level, int agent)
{
    int type = (force_type == NUM_RINGS)     ? get_random_ring_type() :
               (force_type == NUM_JEWELLERY) ? get_random_amulet_type()
                                             : force_type;
    if (item_level > 0
        && one_chance_in(20)
        && x_chance_in_y(101 + item_level * 3, 2000))
    {
        if (_try_make_item_unrand(item, type, item_level, agent))
            return true;
    }

    return false;
}

/**
 * Generate an appropriate 'plus' value for a given type of jewellery.
 * Currently only rings use non-zero plus values, with stat rings usings
 * GOOD_STAT_RING_PLUS, protection/slaying using GOOD_RING_PLUS, and evasion
 * using its own value.
 *
 * @param subtype       The type of jewellery in question.
 * @return              A 'plus' for that jewellery.
 */
short determine_jewellery_plus(int subtype)
{
    if (!jewellery_type_has_pluses(subtype))
        return 0;

    switch (subtype)
    {
        case RING_STRENGTH:
        case RING_DEXTERITY:
        case RING_INTELLIGENCE:
            return GOOD_STAT_RING_PLUS;
        case RING_EVASION:
            return 5;
        default:
            return GOOD_RING_PLUS;
    }
}

// Choose a random ring type compatible with any fixed artprops. If no ring
// type is compatible, we use the last chosen type and ignore the fixed
// artprops.
static void _roll_ring_type(item_def& item)
{
    for (int i = 0; i < 1000; ++i)
    {
        item.sub_type = get_random_ring_type();
        if (are_fixed_props_ok(item))
            return;
    }
}

// Choose a random amulet type compatible with any fixed artprops. If no amulet
// type is compatible, we use the last chosen type and ignore the fixed
// artprops.
static void _roll_amulet_type(item_def& item)
{
    for (int i = 0; i < 1000; ++i)
    {
        item.sub_type = get_random_amulet_type();
        if (are_fixed_props_ok(item))
            return;
    }
}

static void _generate_jewellery_item(item_def& item, bool allow_uniques,
                                     int force_type, int item_level,
                                     int agent)
{
    if (allow_uniques && item_level != ISPEC_RANDART
        && _try_make_jewellery_unrandart(item, force_type, item_level, agent))
    {
        return;
    }

    // Determine subtype.
    if (force_type != OBJ_RANDOM
        // These values request a random ring or a random amulet, respectively.
        && force_type != NUM_RINGS
        && force_type != NUM_JEWELLERY)
    {
        item.sub_type = force_type;
    }
    else if (force_type == NUM_RINGS
             || force_type == OBJ_RANDOM && !one_chance_in(4))
    {
        _roll_ring_type(item);
    }
    else
        _roll_amulet_type(item);

    item.plus = determine_jewellery_plus(item.sub_type);

    // All jewellery base types should now work. - bwr
    if (item_level == ISPEC_RANDART
        || allow_uniques && item_level > 0
           && x_chance_in_y(101 + item_level * 3, 4000))
    {
        make_item_randart(item);
    }
}

/// For a given dungeon depth (or item level), how much weight should we give
/// to each talisman tier?
static const vector<random_pick_entry<int>> talisman_weights =
{
    {  0, 10,  65, FALL, 1 },
    {  0, 35,   5, FLAT, 1 },

    {  -5, 20, 56, PEAK, 2 },
    {  0, 35,  12, FLAT, 2 },

    {  5, 22,  30, RISE, 3 },
    { 21, 35,  30, FLAT, 3 },
    {  0, 35,   5, FLAT, 3 },

    { 12, 27,  24, RISE, 4 },
    { 28, 35,  24, FLAT, 4 },
    {  0, 35,   5, FLAT, 4 },

    { 20, 27,  15, RISE, 5 },
    { 28, 35,  15, FLAT, 5 },
    {  0, 35,   5, FLAT, 5 },
};

static int _talisman_level(int item_level)
{
    switch (item_level) {
    case ISPEC_DAMAGED:
    case ISPEC_BAD:
        return 0;
    case ISPEC_GIFT:
        return 15; // ?? arbitrary
    case ISPEC_RANDART:
    case ISPEC_GOOD_ITEM:
        return 25; // Relatively even mix of higher-tier options
    default:
        return min(item_level, 35); // roughly the bottom of the Hells
    }
}

// Choose a random talisman type compatible with any fixed artprops. If no
// talisman type is compatible, we use the last chosen type and ignore the
// fixed artprops.
static void _roll_talisman_type(item_def &item, int lvl)
{
    random_picker<int, 11> picker;

    for (int i = 0; i < 1000; ++i)
    {
        const int tier = picker.pick(talisman_weights, lvl, 3);
        const vector<talisman_type> types = talismans_by_tier(tier);
        item.sub_type = types[random2(types.size())];
        if (are_fixed_props_ok(item))
            return;
    }
}

static void _generate_talisman_item(item_def& item, int force_type, int item_level)
{
    const int lvl = _talisman_level(item_level);

    if (force_type != OBJ_RANDOM)
        item.sub_type = force_type;
    else
        _roll_talisman_type(item, lvl);

    // Make randarts significantly more common for 'low-tier' talisman rolls
    // compared to the expected average at a given item level (very roughly).
    const int tier = talisman_tier(static_cast<talisman_type>(item.sub_type));
    const int randart_chance = max(0, lvl - (tier - 1) * 6) * 5 / 2 + 5;
    if (item_level == ISPEC_RANDART || x_chance_in_y(randart_chance, 100))
        make_item_randart(item);
}

misc_item_type get_misc_item_type(int force_type, bool exclude)
{
    if (force_type != OBJ_RANDOM)
        return (misc_item_type)force_type;

    set<misc_item_type> choices;

    if (exclude)
    {
        choices = {
            (misc_item_type)item_for_set(ITEM_SET_CONTROL_MISCELLANY),
            MISC_LIGHTNING_ROD,
            (misc_item_type)item_for_set(ITEM_SET_ALLY_MISCELLANY),
            MISC_PHANTOM_MIRROR,
            (misc_item_type)item_for_set(ITEM_SET_AREA_MISCELLANY)
        };
    }
    else
    {
        choices = {
            MISC_PHIAL_OF_FLOODS,
            MISC_LIGHTNING_ROD,
            MISC_BOX_OF_BEASTS,
            MISC_SACK_OF_SPIDERS,
            MISC_PHANTOM_MIRROR,
            MISC_CONDENSER_VANE,
            MISC_TIN_OF_TREMORSTONES,
        };
    }
    if (choices.size())
        return *random_iterator(choices);
    return NUM_MISCELLANY;
}

static void _generate_misc_item(item_def& item, int force_type, int item_level)
{
    const auto typ = get_misc_item_type(force_type);
    if (typ == NUM_MISCELLANY)
    {
        item.base_type = OBJ_WANDS;
        generate_wand_item(item, OBJ_RANDOM, item_level);
        return;
    }
    item.sub_type = typ;
}

/**
 * Alter the inputted item to have no "plusses" (mostly weapon/armour enchantment)
 *
 * @param[in,out] item_slot The item slot of the item to remove "plusses" from.
 */
void squash_plusses(int item_slot)
{
    item_def& item(env.item[item_slot]);

    item.plus         = 0;
    item.plus2        = 0;
    item.brand        = 0;
    set_equip_desc(item, ISFLAG_NO_DESC);
}

static bool _ego_unrand_only(int base_type, int ego)
{
    if (base_type == OBJ_WEAPONS)
    {
        switch (static_cast<brand_type>(ego))
        {
        case SPWPN_FOUL_FLAME:
        case SPWPN_ACID:
            return true;
        default:
            return false;
        }
    }
    // all armours are ok?
    return false;
}

// Make a corresponding randart instead as a fallback.
// Attempts to use base type, sub type, and ego (for armour/weapons).
// Artefacts can explicitly specify a base type and fallback type.

// Could be even more flexible: maybe allow an item spec to describe
// complex fallbacks?
static void _setup_fallback_randart(const int unrand_id,
                                    item_def &item,
                                    int &force_type,
                                    int &item_level)
{
    ASSERT(get_unrand_entry(unrand_id));
    const unrandart_entry &unrand = *get_unrand_entry(unrand_id);
    dprf("Placing fallback randart for %s", unrand.name);

    uint8_t fallback_sub_type;
    if (unrand.fallback_base_type != OBJ_UNASSIGNED)
    {
        item.base_type = unrand.fallback_base_type;
        fallback_sub_type = unrand.fallback_sub_type;
    }
    else
    {
        item.base_type = unrand.base_type;
        fallback_sub_type = unrand.sub_type;
    }

    if (item.base_type == OBJ_WEAPONS
        && fallback_sub_type == WPN_STAFF)
    {
        item.base_type = OBJ_STAVES;
        if (unrand_id == UNRAND_OLGREB)
            force_type = STAFF_ALCHEMY;
        else
            force_type = OBJ_RANDOM;
        // XXX: small chance of other unrands under some circumstances...
    }
    else if (item.base_type == OBJ_JEWELLERY
             && fallback_sub_type == AMU_NOTHING)
    {
        force_type = NUM_JEWELLERY;
    }
    else
        force_type = fallback_sub_type;

    // import some brand information from the unrand. TODO: I'm doing this for
    // the sake of vault designers who make themed vaults. But it also often
    // makes the item more redundant with the unrand; maybe add a bit more
    // flexibility in item specs so that this part is optional? However, from a
    // seeding perspective, it also makes sense if the item generated is
    // very comparable.

    // unset fallback_brand is -1. In that case it will try to use the regular
    // brand if there is one. If the end result here is 0, the brand (for
    // weapons) will be chosen randomly. So to force randomness, use
    // SPWPN_NORMAL on the fallback ego, or set neither. (Randarts cannot be
    // unbranded.)
    item.brand = unrand.fallback_brand; // no type checking here...

    if (item.brand < 0
        && !_ego_unrand_only(item.base_type, unrand.prpty[ARTP_BRAND])
        && item.base_type == unrand.base_type // brand isn't well-defined for != case
        && ((item.base_type == OBJ_WEAPONS
             && is_weapon_brand_ok(force_type, unrand.prpty[ARTP_BRAND], true))
            || (item.base_type == OBJ_ARMOUR
             && is_armour_brand_ok(force_type, unrand.prpty[ARTP_BRAND], true))))
    {
        // maybe do jewellery too?
        item.brand = unrand.prpty[ARTP_BRAND];
    }
    if (item.brand < 0)
        item.brand = 0;

    item_level = ISPEC_RANDART;
}

/**
 * Create an item.
 *
 * Various parameters determine whether the item can be an artifact, set the
 * item class (ex. weapon, wand), set the item subtype (ex.
 * hand axe, wand of flame), set the item ego (ex. of flaming, of running), set
 * the rough power level of the item, and set the agent of the item (which
 * affects what artefacts can be generated, and also non-artefact items if the
 * agent is Xom). Item class, Item type, and Item ego can also be randomly
 * selected (by setting those parameters to OBJ_RANDOM, OBJ_RANDOM, and 0
 * respectively).
 *
 * @param allow_uniques Can the item generated be an artefact?
 * @param force_class The desired OBJECTS class (Example: OBJ_ARMOUR)
 * @param force_type The desired SUBTYPE - enum varies by OBJ
 * @param item_level How powerful the item is allowed to be
 * @param force_ego The desired ego/brand
 * @param agent The agent creating the item (Example: Xom) or -1 if NA
 * @param custom_name A custom name for the item
 * @param props Any special item props
 *
 * @return The generated item's item slot or NON_ITEM if it fails.
 */
int items(bool allow_uniques,
          object_class_type force_class,
          int force_type,
          int item_level,
          int force_ego,
          int agent,
          string custom_name,
          CrawlHashTable const *fixed_props)
{
    rng::subgenerator item_rng;

    ASSERT(force_ego <= 0
           || force_class == OBJ_WEAPONS
           || force_class == OBJ_ARMOUR
           || force_class == OBJ_MISSILES
           || force_class == OBJ_MISCELLANY);

    // Find an empty slot for the item (with culling if required).
    int p = get_mitm_slot(10);
    if (p == NON_ITEM)
        return NON_ITEM;

    item_def& item(env.item[p]);

    const bool force_good = item_level >= ISPEC_GIFT;


    item.brand = force_ego;

    // cap item_level unless an acquirement-level item {dlb}:
    if (item_level > 50 && !force_good)
        item_level = 50;

    // determine base_type for item generated {dlb}:
    if (force_class != OBJ_RANDOM)
    {
        ASSERT(force_class < NUM_OBJECT_CLASSES);
        item.base_type = force_class;
    }
    else
    {
        ASSERT(force_type == OBJ_RANDOM);
        // Total weight: 1660
        item.base_type = random_choose_weighted(
                                    10, OBJ_STAVES,
                                    25, OBJ_TALISMANS,
                                    45, OBJ_JEWELLERY,
                                    46, OBJ_MISSILES,
                                    70, OBJ_WANDS,
                                   155, OBJ_BOOKS,
                                   212, OBJ_ARMOUR,
                                   212, OBJ_WEAPONS,
                                   176, OBJ_POTIONS,
                                   270, OBJ_SCROLLS,
                                   440, OBJ_GOLD);

        // misc items placement wholly dependent upon current depth {dlb}:
        if (item_level > 7 && x_chance_in_y(11 + item_level, 9000))
            item.base_type = OBJ_MISCELLANY;

        if (item_level < 7
            && item.base_type == OBJ_WANDS
            && random2(7) >= item_level)
        {
            item.base_type = random_choose(OBJ_POTIONS, OBJ_SCROLLS);
        }

        if (one_chance_in(275) && !x_chance_in_y(item_level, 35))
            item.base_type = OBJ_BAUBLES;
    }

    ASSERT(force_type == OBJ_RANDOM
           || item.base_type == OBJ_JEWELLERY && force_type == NUM_JEWELLERY
           || force_type < get_max_subtype(item.base_type));

    // make_item_randart() might do things differently based upon the
    // acquirement agent, especially for god gifts.
    if (agent != NO_AGENT && !is_stackable_item(item))
        origin_acquired(item, agent);

    item.quantity = 1;          // generally the case

    if (force_ego < SP_FORBID_EGO)
    {
        const int unrand_id = -force_ego;
        if (get_unique_item_status(unrand_id) == UNIQ_NOT_EXISTS)
        {
            make_item_unrandart(env.item[p], unrand_id);
            ASSERT(env.item[p].is_valid());
            return p;
        }

        _setup_fallback_randart(unrand_id, item, force_type, item_level);
        allow_uniques = false;
    }

    if (!custom_name.empty())
        item.props[ITEM_NAME_KEY] = custom_name;

    if (fixed_props)
        item.props[FIXED_PROPS_KEY].get_table() = *fixed_props;

    // Determine sub_type accordingly. {dlb}
    switch (item.base_type)
    {
    case OBJ_WEAPONS:
        _generate_weapon_item(item, allow_uniques, force_type, item_level,
                              agent);
        break;

    case OBJ_MISSILES:
        _generate_missile_item(item, force_type, item_level);
        break;

    case OBJ_ARMOUR:
        _generate_armour_item(item, allow_uniques, force_type, item_level,
                              agent);
        break;

    case OBJ_WANDS:
        generate_wand_item(item, force_type, item_level);
        break;

    case OBJ_POTIONS:
        _generate_potion_item(item, force_type, item_level);
        break;

    case OBJ_SCROLLS:
        _generate_scroll_item(item, force_type, agent);
        break;

    case OBJ_JEWELLERY:
        _generate_jewellery_item(item, allow_uniques, force_type, item_level,
                                 agent);
        break;

    case OBJ_BOOKS:
        _generate_book_item(item, force_type, item_level);
        break;

    case OBJ_STAVES:
        // Don't generate unrand staves this way except through acquirement,
        // since they also generate as OBJ_WEAPONS.
        _generate_staff_item(item, (agent != NO_AGENT), force_type,
                             item_level, agent);
        break;

    case OBJ_ORBS:              // always forced in current setup {dlb}
    case OBJ_RUNES:
        _generate_rune_item(item, force_type);
        break;

    case OBJ_GEMS:
        _generate_gem_item(item, force_type);
        break;

    case OBJ_TALISMANS:
        _generate_talisman_item(item, force_type, item_level);
        break;

    case OBJ_MISCELLANY:
        _generate_misc_item(item, force_type, item_level);
        break;

    case OBJ_GIZMOS:
        item.base_type = OBJ_GIZMOS;
        item.sub_type = 0;
        break;

    case OBJ_BAUBLES:
        item.base_type = OBJ_BAUBLES;
        item.sub_type = BAUBLE_FLUX;
        item.quantity = random_range(2, 3);
        break;

    // that is, everything turns to gold if not enumerated above, so ... {dlb}
    default:
        item.base_type = OBJ_GOLD;
        if (force_good)
            item.quantity = 100 + random2(400);
        else
        {
            item.quantity = 1 + random2avg(19, 2);
            item.quantity += random2(item_level);
        }
        break;
    }

    if (item_has_invalid_brand(item))
    {
        dprf(DIAG_DNGN, "Invalid brand on item %s, annulling.",
            item.name(DESC_PLAIN, false, true, false, false).c_str());

        if (is_artefact(item))
            item.props[ARTEFACT_PROPS_KEY].get_vector()[ARTP_BRAND].get_short() = 0;
        else
            item.brand = 0;
    }

    // Colour the item.
    item_colour(item);

    // Set brand appearance.
    item_set_appearance(item);

    // Don't place the item just yet.
    item.pos.reset();
    item.link = NON_ITEM;

    // Note that item might be invalidated now, since p could have changed.
    ASSERTM(env.item[p].is_valid(),
            "idx: %d, qty: %hd, base: %d, sub: %d, spe: %d, col: %d, rnd: %d",
            item.index(), item.quantity,
            (int)item.base_type, (int)item.sub_type, item.special,
            (int)item.get_colour(), (int)item.rnd);
    return p;
}

void reroll_brand(item_def &item, int item_level)
{
    ASSERT(!is_artefact(item));
    switch (item.base_type)
    {
    case OBJ_WEAPONS:
        item.brand = determine_weapon_brand(item, item_level);
        break;
    case OBJ_MISSILES:
        item.brand = _determine_missile_brand(item, item_level);
        break;
    case OBJ_ARMOUR:
        item.brand = _generate_armour_ego(item);
        break;
    default:
        die("can't reroll brands of this type");
    }
    item_set_appearance(item);
}

bool item_has_invalid_brand(const item_def& item)
{
    if (item.base_type == OBJ_WEAPONS)
        return !is_weapon_brand_ok(item.sub_type, get_weapon_brand(item), false);
    else if (item.base_type == OBJ_ARMOUR)
        return !is_armour_brand_ok(item.sub_type, get_armour_ego_type(item), false);
    else if (item.base_type == OBJ_MISSILES)
        return !is_missile_brand_ok(item.sub_type, item.brand, false);

    return false;
}

static bool _weapon_is_visibly_special(const item_def &item)
{
    const int brand = get_weapon_brand(item);
    const bool visibly_branded = (brand != SPWPN_NORMAL);

    if (get_equip_desc(item) != ISFLAG_NO_DESC)
        return false;

    if (visibly_branded || is_artefact(item) || item.plus > 0)
        return true;

    return false;
}

static bool _armour_is_visibly_special(const item_def &item)
{
    const int brand = get_armour_ego_type(item);
    const bool visibly_branded = (brand != SPARM_NORMAL);

    if (get_equip_desc(item) != ISFLAG_NO_DESC)
        return false;

    if (visibly_branded || is_artefact(item) || item.plus > 0)
        return true;

    return false;
}

// Sets item appearance to match brands, if any.
void item_set_appearance(item_def &item)
{
    // Artefact appearance overrides cosmetic flags anyway.
    if (is_artefact(item))
        return;

    if (get_equip_desc(item) != ISFLAG_NO_DESC)
        return;

    switch (item.base_type)
    {
    case OBJ_WEAPONS:
        if (_weapon_is_visibly_special(item))
            set_equip_desc(item, random_choose(ISFLAG_GLOWING, ISFLAG_RUNED));
        break;

    case OBJ_ARMOUR:
        if (_armour_is_visibly_special(item))
        {
            set_equip_desc(item, random_choose(ISFLAG_GLOWING, ISFLAG_RUNED,
                                               ISFLAG_EMBROIDERED_SHINY));
        }
        break;

    default:
        break;
    }
}

void lucky_upgrade_item(item_def& item)
{
    // Only have a chance to discover a better item on first spotting it (and
    // not for items that are already artefacts - which is harmless, but
    // produces confusing messages).
    if (item.flags & (ISFLAG_SEEN | ISFLAG_ARTEFACT_MASK))
        return;

    // 3-5% chance of upgrading an item.
    if (!you.has_mutation(MUT_LUCKY)
        || !x_chance_in_y(1 + you.get_mutation_level(MUT_LUCKY) * 2, 100))
    {
        return;
    }

    string old_name = uppercase_first(item.name(DESC_THE, false, true));
    bool did_upgrade = false;

    // Need to use the bespoke methods for weapons/armour so that it hands out
    // plusses appropriately (or one ends up with tons of +0 randart armour,
    // which isn't very useable).
    if (item.base_type == OBJ_ARMOUR)
        did_upgrade = _try_make_armour_artefact(item, 0, ISPEC_RANDART, 0);
    else if (item.base_type == OBJ_WEAPONS)
        did_upgrade = _try_make_weapon_artefact(item, 0, ISPEC_RANDART, true, 0);
    else
        did_upgrade = make_item_randart(item);

    if (did_upgrade)
    {
        // Messaging is really weird if we don't do this, and it seems a
        // relatively unimportant freebie.
        identify_item(item);
        mprf("<cyan>Lucky! %s was actually %s</cyan>!", old_name.c_str(), item.name(DESC_THE).c_str());
    }
}

// Drop a fresh net on the ground after an actor somehow escapes from it.
void drop_net_at(const coord_def& pos)
{
    item_def item;
    item.base_type = OBJ_MISSILES;
    item.sub_type  = MI_THROWING_NET;
    item.quantity  = 1;
    set_item_ego_type(item, OBJ_MISSILES, SPMSL_NORMAL);
    item_colour(item);

    copy_item_to_grid(item, pos);
}

#if defined(DEBUG_DIAGNOSTICS) || defined(DEBUG_TESTS)
static int _test_item_level()
{
    switch (random2(10))
    {
    case 0:
        return ISPEC_GOOD_ITEM;
    case 1:
        return ISPEC_DAMAGED;
    case 2:
        return ISPEC_BAD;
    case 3:
        return ISPEC_RANDART;
    default:
        return random2(50);
    }
}

void makeitem_tests()
{
    int i, level;
    item_def item;

    mpr("Running generate_weapon_item tests.");
    for (i = 0; i < 10000; ++i)
    {
        item.clear();
        level = _test_item_level();
        item.base_type = OBJ_WEAPONS;
        item.brand = coinflip() ? SPWPN_NORMAL
                                : random2(NUM_REAL_SPECIAL_WEAPONS);
#if TAG_MAJOR_VERSION == 34
        if (item.brand == SPWPN_ORC_SLAYING
            || item.brand == SPWPN_REACHING
            || item.brand == SPWPN_RETURNING
            || item.brand == SPWPN_CONFUSE
            || item.brand == SPWPN_DRAGON_SLAYING)
        {
            item.brand = SPWPN_FORBID_BRAND;
        }
#endif
        auto weap_type = coinflip() ? OBJ_RANDOM : random2(NUM_WEAPONS);
        _generate_weapon_item(item,
                              coinflip(),
                              weap_type,
                              level);
    }

    mpr("Running generate_armour_item tests.");
    for (i = 0; i < 10000; ++i)
    {
        item.clear();
        level = _test_item_level();
        item.base_type = OBJ_ARMOUR;
        item.brand = coinflip() ? SPARM_NORMAL
                                : random2(NUM_REAL_SPECIAL_ARMOURS);
        int type = coinflip() ? OBJ_RANDOM : random2(NUM_ARMOURS);
#if TAG_MAJOR_VERSION == 34
        if (type == ARM_CAP)
            type = ARM_HAT;
        if (type == ARM_CENTAUR_BARDING)
            type = ARM_BOOTS;
#endif
        _generate_armour_item(item,
                              coinflip(),
                              type,
                              level);
    }
    mpr("Running acquirement tests.");
    // note: without char customization this won't exercise all acquirement
    // code. But this at least gives a baseline.
    for (i = 0; i < 500; ++i)
        make_acquirement_items();

}
#endif
