From 53aa28e1e5db942644811467b2bcfb1cb7e2a857 Mon Sep 17 00:00:00 2001 From: Daneel Trevize Date: Fri, 13 Sep 2019 23:40:36 +0100 Subject: [PATCH] Combine ericsium and more recent work to determine categories from icon paths. --- src/item.cpp | 182 +++++++++++++++++++++------------------------ src/item.h | 2 + src/itemsmanager.h | 2 +- test/testitem.cpp | 12 +-- 4 files changed, 93 insertions(+), 105 deletions(-) diff --git a/src/item.cpp b/src/item.cpp index e52458ec5..ae152ccea 100644 --- a/src/item.cpp +++ b/src/item.cpp @@ -32,6 +32,26 @@ #include "porting.h" #include "itemlocation.h" +const std::array Item::replace_map_ = { + // Category hierarchy 0 replacement map + Item::CategoryReplaceMap({{"Divination", "Divination Cards"}, + {"QuestItems", "Quest Items"}}), + // Category hierarchy 1 replacement map + Item::CategoryReplaceMap({{"BodyArmours", "Body"}, + {"VaalGems", "Vaal"}, + {"AtlasMaps", "2.4"}, + {"act4maps", "2.0"}, + {"OneHandWeapons", "1Hand"}, + {"TwoHandWeapons", "2Hand"}}), + // Category hierarchy 2 replacement map + Item::CategoryReplaceMap({{"OneHandAxes", "Axes"}, + {"OneHandMaces", "Maces"}, + {"OneHandSwords", "Swords"}, + {"TwoHandAxes", "Axes"}, + {"TwoHandMaces", "Maces"}, + {"TwoHandSwords", "Swords"}}) +}; + const std::vector ITEM_MOD_TYPES = { "implicitMods", "enchantMods", "explicitMods", "craftedMods" }; @@ -267,105 +287,72 @@ std::string Item::PrettyName() const { } void Item::CalculateCategories(const rapidjson::Value &json) { - category_ = ""; - if (json.HasMember("category") && json["category"].IsObject()) { - // This object contains a single array who's name is the item's category. The array may contain the item's sub-category - rapidjson::Value::ConstMemberIterator itr = json["category"].MemberBegin(); - category_ = itr->name.GetString(); - category_ = Util::Capitalise(category_); - - if (category_ == "Cards") { - category_ = "Divination cards"; + // Derive item type 'category' hierarchy from icon path. + std::smatch sm; + if (std::regex_search(icon_, sm, std::regex("Art/.*?/(.*)/"))) { + std::string match = sm.str(1); + boost::split(category_vector_,match,boost::is_any_of("/")); + //Compress terms with redundant identifiers + //Weapons.OneHandWeapons.OneHandMaces -> Weapons.OneHand.Maces + size_t min = std::min(category_vector_.size(), replace_map_.size()); + for (size_t i = 0; i < min; i++) { + auto it = replace_map_[i].find(category_vector_[i]); + if (it != replace_map_[i].end()) + category_vector_[i] = it->second; } + } else if (std::regex_search(icon_, sm, std::regex("/gen/image/.*?/Item.png"))) { + // Flask images are dynamically generated by GGG to reflect current charge status, rather than live under /Art/2DItems/ + category_vector_.push_back("Flasks"); + } else { + category_vector_.push_back("Unknown"); + } - category_vector_.push_back(category_); - if (itr->value.IsArray() && !itr->value.Empty()) { - // Handle sub-categories - - if (category_ == "Accessories") { // Elevate accessories sub-categories to their own top level categories - category_vector_.pop_back(); - // If amulet and .HasMember("talismanTier") (which is also checked after CalculateCategories()), add Talisman sub-category? - } - - /* - If Armour.Chests, rename Armour.BodyArmour? - If Flasks, use Base64 encoded JSON within icon path to determine sub-category? - */ - - category_ = itr->value[0].GetString(); - category_ = Util::Capitalise(category_); - - if (category_ == "Activegem") { - // Rename these sub-categories - category_ = "Skill"; - if (icon_.find("/Art/2DItems/Gems/VaalGems/") != std::string::npos) { - category_vector_.push_back(category_); - category_ = "Vaal"; - } - } else if (category_ == "Supportgem") { - category_ = "Support"; - } else if (category_ == "Bow" || category_ == "Staff") { - // Sub-categories these categories under handedness - category_vector_.push_back("TwoHand"); - } else if (category_ == "Claw" || category_ == "Dagger" || category_ == "Sceptre" || category_ == "Wand") { - category_vector_.push_back("OneHand"); - } else if (category_ == "Oneaxe") { - // Sub-categories these categories under handedness and rename them - category_vector_.push_back("OneHand"); - category_ = "Axe"; - } else if (category_ == "Onemace") { - category_vector_.push_back("OneHand"); - category_ = "Mace"; - } else if (category_ == "Onesword") { - category_vector_.push_back("OneHand"); - category_ = "Sword"; - } else if (category_ == "Twoaxe") { - category_vector_.push_back("TwoHand"); - category_ = "Axe"; - } else if (category_ == "Twomace") { - category_vector_.push_back("TwoHand"); - category_ = "Mace"; - } else if (category_ == "Twosword") { - category_vector_.push_back("TwoHand"); - category_ = "Sword"; - } - - category_vector_.push_back(category_); - } else if (category_ == "Maps") { - // Use icon path to determine possible expansion sub-category - std::smatch sm; - if (std::regex_search(icon_, sm, std::regex("/Art/2DItems/Maps/(.*)/"))) { - std::string match = sm.str(1); - std::vector iconsubs; - boost::split(iconsubs, match, boost::is_any_of("/")); - std::string sub = iconsubs[0]; - if (sub == "Atlas2Maps") { - category_vector_.push_back("3.1"); - if (iconsubs.size() > 1) { - sub = iconsubs[1]; // Typically "New" - sub = Util::Capitalise(sub); - category_vector_.push_back(sub); - } - } else if (sub == "AtlasMaps") { - category_vector_.push_back("2.4"); - } else if (sub == "act4maps") { - category_vector_.push_back("2.0"); - } else { - Util::Capitalise(sub); - category_vector_.push_back(sub); + if (category_vector_[0] == "Jewels") { + if (std::regex_search(icon_, sm, std::regex("/Jewels/.+?Eye.png"))) { + category_vector_.push_back("Abyss"); + } + } else if (json.HasMember("prophecyText") && json["prophecyText"].IsString()) { + // Relocate Prophecies out from Currencies + category_vector_[0] = "Prophecies"; + } else if (category_vector_[0] == "Maps") { + if (std::regex_search(icon_, sm, std::regex("Maps/(?:Uber)?Vaal(?:[[:digit:]]){2}.png"))) { + // Check for digits immediately after Vaal, to avoid matching on VaalCity (AKA Ancient City), VaalTemple + category_vector_.push_back("Atziri Fragments"); + // What about Prophecy key framents, atlas guardian drop fragments, scarabs, divine vessels? For now most end up under Misc (GGG classed scarabs as currency in their icon path) + } else if (std::regex_search(icon_, sm, std::regex("Maps/(?:.*)Shard.png"))) { + // Check for Legion splinters, recategorise as currency. What about Emblems, need an example to test. + category_vector_[0] = "Currency"; + category_vector_.push_back("Legion"); + } else { + if (category_vector_.size() > 1) { + if (category_vector_[1] == "Atlas2Maps") { + // Try to give newer maps a more useful category name + category_vector_[1] = "3"; + if (category_vector_.size() > 2 ) { + // More accurately 3.1+ + if (category_vector_[2] == "New") { + category_vector_[2] = "1"; + // Calculate release version from ?mn= parameter + if (std::regex_search(icon_, sm, std::regex("&mn=([[:digit:]])+"))) { + int mn = std::stoi(sm[1], nullptr, 10); + if (mn > 1) { + // mn became 2 for 3.5.0 + category_vector_[2] = std::to_string(mn + 3); + } + } + } + } else { + category_vector_.push_back("0"); // Were any added to Atlas2Maps but not New? + } } } else { if (icon_.find("/Art/2DItems/Maps/Map") != std::string::npos) { - // Doesn't find all because of some maps like FairgravesMap01.png, olmec.png, etc. They'll end up in Maps.Misc, then moved to Maps.Older Uniques + // Doesn't find all because of some maps like FairgravesMap01.png, olmec.png, etc. We'll pick them out of Misc later category_vector_.push_back("Original"); - } else if (icon_.find("/Art/2DItems/Currency/Breach/") != std::string::npos) { - // Isn't really a map, has no property named Map Tier - category_vector_.pop_back(); - category_vector_.push_back("Currency"); - category_vector_.push_back("Breach"); } else { category_vector_.push_back("Misc"); + // Check that these aren't just badly named actual maps if (json.HasMember("properties") && json["properties"].IsArray()) { for (auto &prop : json["properties"]) { if (!prop.HasMember("name") || !prop["name"].IsString() || !prop.HasMember("values") || !prop["values"].IsArray()) @@ -373,22 +360,21 @@ void Item::CalculateCategories(const rapidjson::Value &json) { std::string name = prop["name"].GetString(); if (name == "Map Tier") { category_vector_.pop_back(); - category_vector_.push_back("Older Uniques"); // Future ones might fall under a new /Maps/ icon path + category_vector_.push_back("Older Uniques"); break; } - // Else determine if Sacrifice/Mortal/Offering/etc fragment? } } } } - } else if (category_ == "Currency") { - if (icon_.find("/Art/2DItems/Currency/Breach/") != std::string::npos) { - category_vector_.push_back("Breach"); - } else if (icon_.find("/Art/2DItems/Currency/Essence/") != std::string::npos) { - category_vector_.push_back("Essence"); - } } } + + if (category_vector_.size() > 1 && category_vector_[1] == "Scarabs") { + category_vector_[0] = "Maps"; + // Technically scarabs are fragments. Better to leave Fragments as just Atziri ones, give scarabs their own subcategory? + } + category_ = boost::join(category_vector_, "."); boost::to_lower(category_); diff --git a/src/item.h b/src/item.h index e6a72a7b4..b03795348 100644 --- a/src/item.h +++ b/src/item.h @@ -115,6 +115,8 @@ class Item { bool operator<(const Item &other) const; bool Wearable() const; std::string POBformat() const; + static const size_t k_CategoryLevels = 3; + static const std::array replace_map_; private: void CalculateCategories(const rapidjson::Value &json); diff --git a/src/itemsmanager.h b/src/itemsmanager.h index 3e843aeeb..44a7c10a5 100644 --- a/src/itemsmanager.h +++ b/src/itemsmanager.h @@ -55,7 +55,7 @@ class ItemsManager : public QObject { void ApplyAutoItemBuyouts(); void PropagateTabBuyouts(); void UpdateCategories(); - const QSet& categories() const { return categories_; }; + const QSet& categories() const { return categories_; } public slots: // called by auto_update_timer_ void OnAutoRefreshTimer(); diff --git a/test/testitem.cpp b/test/testitem.cpp index 7f05760dd..0216dc101 100644 --- a/test/testitem.cpp +++ b/test/testitem.cpp @@ -51,7 +51,7 @@ void TestItem::ParseCategories() { doc.Parse(kCategoriesItemBelt.c_str()); item = Item(doc); - QCOMPARE(item.category().c_str(), "belt"); + QCOMPARE(item.category().c_str(), "belts"); doc.Parse(kCategoriesItemEssence.c_str()); item = Item(doc); @@ -59,7 +59,7 @@ void TestItem::ParseCategories() { doc.Parse(kCategoriesItemVaalGem.c_str()); item = Item(doc); - QCOMPARE(item.category().c_str(), "gems.skill.vaal"); + QCOMPARE(item.category().c_str(), "gems.vaal"); doc.Parse(kCategoriesItemSupportGem.c_str()); item = Item(doc); @@ -67,19 +67,19 @@ void TestItem::ParseCategories() { doc.Parse(kCategoriesItemBow.c_str()); item = Item(doc); - QCOMPARE(item.category().c_str(), "weapons.twohand.bow"); + QCOMPARE(item.category().c_str(), "weapons.2hand.bows"); doc.Parse(kCategoriesItemClaw.c_str()); item = Item(doc); - QCOMPARE(item.category().c_str(), "weapons.onehand.claw"); + QCOMPARE(item.category().c_str(), "weapons.1hand.claws"); doc.Parse(kCategoriesItemFragment.c_str()); item = Item(doc); - QCOMPARE(item.category().c_str(), "maps.misc"); + QCOMPARE(item.category().c_str(), "maps.atziri fragments"); doc.Parse(kCategoriesItemWarMap.c_str()); item = Item(doc); - QCOMPARE(item.category().c_str(), "maps.3.1.new"); + QCOMPARE(item.category().c_str(), "maps.3.1"); doc.Parse(kCategoriesItemUniqueMap.c_str()); item = Item(doc);