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 -> magireturn 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 -> simulacrareturn 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_conditionmenu_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);elses.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 -> magireturn 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 -> simulacrareturn 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 andpickup menus. When set to false (the default), items are ordered byequipment slot.When set to a number - using a syntax of the formsort_menus = auto:5- items are sorted by their description if the total number of items inthat category is at least that number; in the example, having 4kinds 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 orderthey'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 leastX items in the same category. For instance:sort_menus = auto:5will sort item classes that have at least 5 items. Forinstance, having 4 kinds of potions would not sort them, buthaving 5 would.You can explicitly specify sort criteria in the sort_menusoption as:sort_menus = true : basename, qualname, curse, qtyThe 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 robeof Ponies" is just "robe".* qualname:The name of the item without articles (a/an/the),quantities, enchantments, or curse-status. The qualifiednames for the robes described above are "robe", "embroideredrobe" 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 upfirst.* qty:The quantity for stackable items (such as scrolls, potions,etc.)* slot:The inventory letter for items in inventory; irrelevant foritems on the floor.The default sort criteria are: "basename, qualname, curse, qty".You can ask for a descending order sort by prefixing one ormore sort criteria with > as:sort_menus = true : basename, >qtyYou can also request sorting only for specific menus:sort_menus = pickup: trueorsort_menus = inv: true(Menu types must be specified as name:, with no space betweenname and colon.)The menu selectors available are:pickup: All pickup menus, stash-search menus, etc. for items notin your inventory.drop: The item drop menu.inv: Inventory listings for any command (but not for droppingitems).any: All menus; this is the default when unspecified.