sort items, at the cost of obfuscating the sort_menus option massively.
git-svn-id: https://crawl-ref.svn.sourceforge.net/svnroot/crawl-ref/trunk@1494 c06c8d41-db1a-0410-9941-cceddc491573
BWAQ3FHBBM6G3K3KYP75CRTR343RDQZJRYX5ZGYUEXYBAC3APDLAC
ZA7XPDPN44QXOAE5UKRQKMCJCC4IOFTW24CWRFIBKNJBCS7MRR5AC
BWLTFEAHYDDL4I45B6J55SDLPNN5L6BGFD4KPNEUSP7PRQBY5AUQC
XS4OT3JJKMXJIOMIGSSHIE4IOV2EXKFFELHEU7J2C2B7PKAP4V4QC
6GDKXNFXPKQ6AVNOSMJECN7CLELM2KCMVRM2A7BARLK2NNILN6SAC
XYJ5Y635KCRCTL2BFPP53NWQRK3NUHS6T3ARPIXV5A3CHWBW7VCQC
DTJNZWOY2ODLIKWXJXEXOABVO2NDU7DM4UZ3NVLHXPQORVNFPTJQC
E5DMZFW6WCFAKTKKOQPYTQXZ2CGLWMVH64LRXDUI2UIG4VYUHIVQC
WW6THKR7JN447YC23YYHYYNH7ABMCFFSECNUFTIJBZX6JHX6W7TAC
SDLKLUNFGVKDS55DDJZCBAVIB7NL3RRYPTACAY65SCUQKV6APFSAC
RPOZZWKG5GLPHVZZ7ZKMKS64ZMV2LDCQSARBJFJ6FZOTOKCQO7FAC
OYBY6LKWPS5PWGZ7CQOKB4NPQFLD4V2W4PDKBFTFSDG2NI3DD6AAC
K2CS6TCX2NDVL2ASEHGP4J4K4IJ6FP3ANNKTSIWVG43HPYSBX6ZQC
2NCKGJDDPPGP2NXYYPEPVRJIIWEP6M7HE6WYMQN3UNNN3C2JIRFQC
VNHFP63ZLLZU3A3PLXP4BITX57DUIYDHFOHQYK3BOBHV3S64G26QC
2KTJHQUX2LTU2BCLS5YXVRRKMOYKKIZAPF2LBKORFGSHEN5IO3IAC
6NTCURCJQA4PBNDD5VGFBQAF5QCZZPLWRDZTA65R3EOGMVA475IAC
CIPVRZGLOZHCERK6YPOBV3P2E4IAB4H6D5EHLRQE2O5E4P4VCBUAC
77H4BWWPPGLM3PLZH4QTAJRXIZTSDVNCOKZE223I437FN2UJ34RQC
VDNTHQOGDJKBTO7MKRCYH5MCOPCUWHFLPHAWDYDUIV7JKTKENBYQC
GNJGG33CNP6IWUW4V2JKIFAC5N43TP5MX4PZOTXROBYZVXQEAJLAC
5RK245FAGZFCDDYG4AZAXSC7JPVIJG4DSAVAKHWWVBUNGICHYNJQC
V4WGXVERZ34B7CEINV4D3ZYEKKT2TUIUYTOX5FSOX6B26U3DPVLQC
KU6YUIJWXZSNTABFB36HDEEZ6LTY7O4VKI3RNFBQE3LEYZCT3XQQC
547JREUJXTZNYVGHNNAET5F5O5JYYGNTDQB6ABZNT7YX5EY64OHAC
QDTVLBRGHDTRUVT7I3O72K6TMOYAUSAJBZUHGOEFU2RKJNUPWZSQC
W5VEC2PBIM5DMU5233HOWAZUEPTGWJRZZIA3H35YYQQW6BTP6XUAC
CRCKW7MAFIP2MB6ZNPVZXUHBGSPQNTYHGDVF2TCM2K6XLRUTUW4QC
RVST2QHYJ757ZHK4AUJ5NGPDZ44AD6RVFVXYPKQIBJXZBDNUCHXQC
EHSY6DVGUMI6C67WKET3GDJVLWJWGYBYQONNDK5JVT7BCTHBEZVAC
XPCGZBHHSL6MB3ORMUJI64BAERU6AZTIY6RK56BBW7SNB3IK24IAC
}
// Pluralises a monster or item name. This'll need to be updated for
// correctness whenever new monsters/items are added.
std::string pluralise(const std::string &name,
const char *no_of[])
{
std::string::size_type pos;
// Pluralise first word of names like 'eye of draining' or
// 'scrolls labeled FOOBAR', but only if the whole name is not
// suffixed by a supplied modifier, such as 'zombie' or 'skeleton'
if ( (pos = name.find(" of ")) != std::string::npos
&& !ends_with(name, no_of) )
return pluralise(name.substr(0, pos)) + name.substr(pos);
else if ( (pos = name.find(" labeled ")) != std::string::npos
&& !ends_with(name, no_of) )
return pluralise(name.substr(0, pos)) + name.substr(pos);
else if (ends_with(name, "us"))
// Fungus, ufetubus, for instance.
return name.substr(0, name.length() - 2) + "i";
else if (ends_with(name, "larva") || ends_with(name, "amoeba"))
// Giant amoebae sounds a little weird, to tell the truth.
return name + "e";
else if (ends_with(name, "ex"))
// Vortex; vortexes is legal, but the classic plural is cooler.
return name.substr(0, name.length() - 2) + "ices";
else if (ends_with(name, "cyclops"))
return name.substr(0, name.length() - 1) + "es";
else if (ends_with(name, "y"))
return name.substr(0, name.length() - 1) + "ies";
else if (ends_with(name, "elf") || ends_with(name, "olf"))
// Elf, wolf. Dwarfs can stay dwarfs, if there were dwarfs.
return name.substr(0, name.length() - 1) + "ves";
else if (ends_with(name, "mage"))
// mage -> magi
return name.substr(0, name.length() - 1) + "i";
else if ( ends_with(name, "sheep") || ends_with(name, "manes")
|| ends_with(name, "fish") )
// Maybe we should generalise 'manes' to ends_with("es")?
return name;
else if (ends_with(name, "ch") || ends_with(name, "sh")
|| ends_with(name, "x"))
// To handle cockroaches, fish and sphinxes. Fish will be netted by
// the previous check anyway.
return name + "es";
else if (ends_with(name, "um"))
// simulacrum -> simulacra
return name.substr(0, name.length() - 2) + "a";
else if (ends_with(name, "efreet"))
// efreet -> efreeti. Not sure this is correct.
return name + "i";
return name + "s";
const bool know_curse = ident || item_ident(*this, ISFLAG_KNOW_CURSE);
const bool basename = desc == DESC_BASENAME;
const bool qualname = desc == DESC_QUALNAME;
const bool know_curse =
!basename && !qualname
&& (ident || item_ident(*this, ISFLAG_KNOW_CURSE));
// always give racial type (it does have game effects)
buff << racial_description_string(*this, terse);
if (!basename)
{
// always give racial type (it does have game effects)
buff << racial_description_string(*this, terse);
// always give racial description (has game effects)
buff << racial_description_string(*this, terse);
if (!basename)
{
// always give racial description (has game effects)
buff << racial_description_string(*this, terse);
}
buff << "chunk";
if (this->quantity > 1)
buff << "s";
const std::string &InvEntry::get_basename() const
{
if (basename.empty())
basename = item->name(DESC_BASENAME);
return (basename);
}
const std::string &InvEntry::get_qualname() const
{
if (qualname.empty())
qualname = item->name(DESC_QUALNAME);
return (qualname);
}
const std::string &InvEntry::get_fullname() const
{
return (text);
}
const bool InvEntry::is_item_cursed() const
{
return (item_ident(*item, ISFLAG_KNOW_CURSE) && item_cursed(*item));
}
}
template <std::string (*proc)(const InvEntry *a)>
int compare_item_str(const InvEntry *a, const InvEntry *b)
{
return (proc(a).compare(proc(b)));
}
template <typename T, T (*proc)(const InvEntry *a)>
int compare_item(const InvEntry *a, const InvEntry *b)
{
return (int(proc(a)) - int(proc(b)));
return (*a < *b);
return a->get_qualname();
}
std::string sort_item_fullname(const InvEntry *a)
{
return a->get_fullname();
}
int sort_item_qty(const InvEntry *a)
{
return a->quantity;
}
int sort_item_slot(const InvEntry *a)
{
return a->item->link;
}
bool sort_item_curse(const InvEntry *a)
{
return a->is_item_cursed();
}
static bool compare_invmenu_items(const InvEntry *a, const InvEntry *b,
const item_sort_comparators *cmps)
{
for (item_sort_comparators::const_iterator i = cmps->begin();
i != cmps->end();
++i)
{
const int cmp = i->compare(a, b);
if (cmp)
return (cmp < 0);
}
return (false);
}
struct menu_entry_comparator
{
const menu_sort_condition *cond;
menu_entry_comparator(const menu_sort_condition *c)
: cond(c)
{
}
bool operator () (const MenuEntry* a, const MenuEntry* b) const
{
const InvEntry *ia = dynamic_cast<const InvEntry *>(a);
const InvEntry *ib = dynamic_cast<const InvEntry *>(b);
return compare_invmenu_items(ia, ib, &cond->cmp);
}
};
void init_item_sort_comparators(item_sort_comparators &list,
const std::string &set)
{
static struct
{
const std::string cname;
item_sort_fn cmp;
} cmp_map[] =
{
{ "basename", compare_item_str<sort_item_basename> },
{ "qualname", compare_item_str<sort_item_qualname> },
{ "fullname", compare_item_str<sort_item_fullname> },
{ "curse", compare_item<bool, sort_item_curse> },
{ "qty", compare_item<int, sort_item_qty> },
{ "slot", compare_item<int, sort_item_slot> },
};
list.clear();
std::vector<std::string> cmps = split_string(",", set);
for (int i = 0, size = cmps.size(); i < size; ++i)
{
std::string s = cmps[i];
if (s.empty())
continue;
const bool negated = s[0] == '>';
if (s[0] == '<' || s[0] == '>')
s = s.substr(1);
for (unsigned ci = 0; ci < ARRAYSIZE(cmp_map); ++ci)
if (cmp_map[ci].cname == s)
{
list.push_back( item_comparator( cmp_map[ci].cmp, negated ) );
break;
}
}
if (list.empty())
list.push_back(
item_comparator(compare_item_str<sort_item_fullname>));
}
const menu_sort_condition *InvMenu::find_menu_sort_condition() const
{
for (int i = 0, size = Options.sort_menus.size(); i < size; ++i)
if (Options.sort_menus[i].matches(type))
return &Options.sort_menus[i];
return (NULL);
}
void InvMenu::sort_menu(std::vector<InvEntry*> &invitems,
const menu_sort_condition *cond)
{
if (!cond || cond->sort == -1 || (int) invitems.size() < cond->sort)
return;
std::sort( invitems.begin(), invitems.end(), menu_entry_comparator(cond) );
if (Options.sort_menus != -1 &&
(int)items_in_class.size() >= Options.sort_menus)
std::sort( items_in_class.begin(), items_in_class.end(),
compare_menu_entries );
sort_menu(items_in_class, cond);
///////////////////////////////////////////////////////////////////////
// menu_sort_condition
menu_sort_condition::menu_sort_condition(menu_type _mt, int _sort)
: mtype(_mt), sort(_sort), cmp()
{
}
menu_sort_condition::menu_sort_condition(const std::string &s)
: mtype(MT_ANY), sort(-1), cmp()
{
std::string cp = s;
set_menu_type(cp);
set_sort(cp);
set_comparators(cp);
}
bool menu_sort_condition::matches(menu_type mt) const
{
return (mtype == MT_ANY || mtype == mt);
}
void menu_sort_condition::set_menu_type(std::string &s)
{
static struct
{
const std::string mname;
menu_type mtype;
} menu_type_map[] =
{
{ "any:", MT_ANY },
{ "inv:", MT_INVLIST },
{ "drop:", MT_DROP },
{ "pickup:", MT_PICKUP }
};
for (unsigned mi = 0; mi < ARRAYSIZE(menu_type_map); ++mi)
{
const std::string &name = menu_type_map[mi].mname;
if (s.find(name) == 0)
{
s = s.substr(name.length());
mtype = menu_type_map[mi].mtype;
break;
}
}
}
void menu_sort_condition::set_sort(std::string &s)
{
// Strip off the optional sort clauses and get the primary sort condition.
std::string::size_type trail_pos = s.find(':');
if (s.find("auto:") == 0)
trail_pos = s.find(':', trail_pos + 1);
std::string sort_cond =
trail_pos == std::string::npos? s : s.substr(0, trail_pos);
trim_string(sort_cond);
sort = read_bool_or_number(sort_cond, sort, "auto:");
if (trail_pos != std::string::npos)
s = s.substr(trail_pos + 1);
else
s.clear();
}
void menu_sort_condition::set_comparators(std::string &s)
{
init_item_sort_comparators(
cmp,
s.empty()? "basename, qualname, curse, qty" : s);
}
};
class InvEntry;
typedef int (*item_sort_fn)(const InvEntry *a, const InvEntry *b);
struct item_comparator
{
item_sort_fn cmpfn;
bool negated;
item_comparator(item_sort_fn cfn, bool neg = false)
: cmpfn(cfn), negated(neg)
{
}
int compare(const InvEntry *a, const InvEntry *b) const
{
return (negated? -cmpfn(a, b) : cmpfn(a, b));
}
};
typedef std::vector<item_comparator> item_sort_comparators;
struct menu_sort_condition
{
public:
menu_type mtype;
int sort;
item_sort_comparators cmp;
public:
menu_sort_condition(menu_type mt = MT_INVLIST, int sort = 0);
menu_sort_condition(const std::string &s);
bool matches(menu_type mt) const;
private:
void set_menu_type(std::string &s);
void set_sort(std::string &s);
void set_comparators(std::string &s);
// Pluralises a monster name. This'll need to be updated for correctness
// whenever new monsters are added.
std::string pluralise(const std::string &name,
const char *no_of[])
{
std::string::size_type pos;
// Pluralise first word of names like 'eye of draining', but only if the
// whole name is not suffixed by a modifier, such as 'zombie' or 'skeleton'
if ( (pos = name.find(" of ")) != std::string::npos
&& !ends_with(name, no_of) )
return pluralise(name.substr(0, pos)) + name.substr(pos);
else if (ends_with(name, "us"))
// Fungus, ufetubus, for instance.
return name.substr(0, name.length() - 2) + "i";
else if (ends_with(name, "larva") || ends_with(name, "amoeba"))
// Giant amoebae sounds a little weird, to tell the truth.
return name + "e";
else if (ends_with(name, "ex"))
// Vortex; vortexes is legal, but the classic plural is cooler.
return name.substr(0, name.length() - 2) + "ices";
else if (ends_with(name, "cyclops"))
return name.substr(0, name.length() - 1) + "es";
else if (ends_with(name, "y"))
return name.substr(0, name.length() - 1) + "ies";
else if (ends_with(name, "elf") || ends_with(name, "olf"))
// Elf, wolf. Dwarfs can stay dwarfs, if there were dwarfs.
return name.substr(0, name.length() - 1) + "ves";
else if (ends_with(name, "mage"))
// mage -> magi
return name.substr(0, name.length() - 1) + "i";
else if ( ends_with(name, "sheep") || ends_with(name, "manes")
|| ends_with(name, "fish") )
// Maybe we should generalise 'manes' to ends_with("es")?
return name;
else if (ends_with(name, "ch") || ends_with(name, "sh")
|| ends_with(name, "x"))
// To handle cockroaches, fish and sphinxes. Fish will be netted by
// the previous check anyway.
return name + "es";
else if (ends_with(name, "um"))
// simulacrum -> simulacra
return name.substr(0, name.length() - 2) + "a";
else if (ends_with(name, "efreet"))
// efreet -> efreeti. Not sure this is correct.
return name + "i";
sort_menus = (true | false | auto:X)
When set to true, items are sorted by description in inventory and
pickup menus. When set to false (the default), items are ordered by
equipment slot.
When set to a number - using a syntax of the form
sort_menus = auto:5
- items are sorted by their description if the total number of items in
that category is at least that number; in the example, having 4
kinds of potions would not sort them, but having 5 would.
When sort_menus = false (the default), items are not sorted,
and will be ordered by inventory letter (or in the order
they're stacked for items on the floor).
When sort_menus = true, items are sorted by base name,
qualified name, curse status and quantity.
If sort_menus = auto:X, items are sorted if there are at least
X items in the same category. For instance:
sort_menus = auto:5
will sort item classes that have at least 5 items. For
instance, having 4 kinds of potions would not sort them, but
having 5 would.
You can explicitly specify sort criteria in the sort_menus
option as:
sort_menus = true : basename, qualname, curse, qty
The available sort criteria are:
* basename:
This is the name of the item type. The basename for all of
"a +0 robe", "an embroidered robe" and "the cursed +2 robe
of Ponies" is just "robe".
* qualname:
The name of the item without articles (a/an/the),
quantities, enchantments, or curse-status. The qualified
names for the robes described above are "robe", "embroidered
robe" and "robe of Ponies", respectively.
* fullname:
This is the name of the item as displayed in menus
(including quantities, curse-status, etc.)
* curse:
Curse-status of the item (if known). Uncursed items show up
first.
* qty:
The quantity for stackable items (such as scrolls, potions,
etc.)
* slot:
The inventory letter for items in inventory; irrelevant for
items on the floor.
The default sort criteria are: "basename, qualname, curse, qty".
You can ask for a descending order sort by prefixing one or
more sort criteria with > as:
sort_menus = true : basename, >qty
You can also request sorting only for specific menus:
sort_menus = pickup: true
or
sort_menus = inv: true
(Menu types must be specified as name:, with no space between
name and colon.)
The menu selectors available are:
pickup: All pickup menus, stash-search menus, etc. for items not
in your inventory.
drop: The item drop menu.
inv: Inventory listings for any command (but not for dropping
items).
any: All menus; this is the default when unspecified.