diff --git a/src/MaaCore/Task/Miscellaneous/CreditShoppingTask.cpp b/src/MaaCore/Task/Miscellaneous/CreditShoppingTask.cpp index 7c5284e9d3..8ab0679de0 100644 --- a/src/MaaCore/Task/Miscellaneous/CreditShoppingTask.cpp +++ b/src/MaaCore/Task/Miscellaneous/CreditShoppingTask.cpp @@ -48,6 +48,24 @@ asst::CreditShoppingTask& asst::CreditShoppingTask::set_reserve_max_credit(bool return *this; } +bool asst::CreditShoppingTask::verify_commodity_name(const TextRect& commodity) const +{ + if (commodity.text.empty()) { + return false; + } + + const auto product_name_task_ptr = Task.get("CreditShop-ProductName"); + Rect name_roi = product_name_task_ptr->roi; + name_roi.x += commodity.rect.x; + name_roi.y += commodity.rect.y; + + OCRer ocr_analyzer(ctrler()->get_image()); + ocr_analyzer.set_roi(name_roi); + ocr_analyzer.set_replace(product_name_task_ptr->replace_map); + ocr_analyzer.set_required({ commodity.text }); + return ocr_analyzer.analyze().has_value(); +} + int asst::CreditShoppingTask::credit_ocr() { cv::Mat credit_image = ctrler()->get_image(); @@ -101,7 +119,7 @@ int asst::CreditShoppingTask::discount_ocr(const asst::Rect& commodity) return utils::chars_to_number(discount, discount_number) ? discount_number : 0; } -bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool credit_ocr_enabled) +bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool should_stop_on_credit) { const cv::Mat& image = ctrler()->get_image(); @@ -120,7 +138,7 @@ bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool cre } const auto& shopping_list = shop_analyzer.get_result(); - for (const Rect& commodity : shopping_list) { + for (const TextRect& commodity : shopping_list) { if (need_exit()) { return false; } @@ -132,7 +150,7 @@ bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool cre } if (!m_is_white_list && m_only_buy_discount) { - int discount = discount_ocr(commodity); + int discount = discount_ocr(commodity.rect); if (discount <= 0) { int credit = credit_ocr(); if (credit > MaxCredit && m_info_credit_full) { @@ -149,12 +167,21 @@ bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool cre // 因动画过渡等原因,ctrler()->click(commodity) 点击商品时可能失败,共可尝试 4 次 // 若点击商品成功,则 CreditShop-BuyIt 的 ProcessTask 应顺利执行并返回 true + bool bought = false; for (int clickCount = 0; clickCount <= 3; ++clickCount) { - ctrler()->click(commodity); + // 点击前复核一次商品名,避免动画或列表偏移导致盲点到错误商品。 + if (verify_commodity_name(commodity)) { + ctrler()->click(commodity.rect); + } if (ProcessTask(*this, { "CreditShop-BuyIt" }).run()) { + bought = true; break; } } + // 多次点击仍未进入购买流程,说明当前商品无法购买,跳过后续 NoMoney / 信用 OCR 检查 + if (!bought) { + continue; + } if (ProcessTask(*this, { "CreditShop-NoMoney" }).set_task_delay(0).set_retry_times(0).run()) { break; @@ -162,7 +189,7 @@ bool asst::CreditShoppingTask::credit_shopping(bool white_list_enabled, bool cre if (need_exit()) { return false; } - if (credit_ocr_enabled) { + if (should_stop_on_credit) { int credit = credit_ocr(); if (credit <= MaxCredit) { // 信用值不再溢出,停止购物 break; diff --git a/src/MaaCore/Task/Miscellaneous/CreditShoppingTask.h b/src/MaaCore/Task/Miscellaneous/CreditShoppingTask.h index 8d6788105f..ea3c63661a 100644 --- a/src/MaaCore/Task/Miscellaneous/CreditShoppingTask.h +++ b/src/MaaCore/Task/Miscellaneous/CreditShoppingTask.h @@ -30,12 +30,13 @@ protected: bool m_only_buy_discount = false; // 设置只购买折扣信用商品(未打折的白名单物品仍会购买) bool m_reserve_max_credit = false; // 设置消耗信用点至300以下时停止购买商品(仍然会购买白名单物品) bool m_info_credit_full = false; // 设置是否在不购买黑名单物品阶段通知信用点溢出 + bool verify_commodity_name(const TextRect& commodity) const; int credit_ocr(); // 用于在信用商店界面识别商品信息左上角的折扣信息 int discount_ocr(const Rect& commodity); - bool credit_shopping(bool white_list_enabled, bool credit_ocr_enabled); + bool credit_shopping(bool white_list_enabled, bool should_stop_on_credit); std::vector m_shopping_list; bool m_is_white_list = false; diff --git a/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.cpp b/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.cpp index f3612a3569..a3c63f4bbd 100644 --- a/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.cpp +++ b/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.cpp @@ -2,6 +2,7 @@ #include +#include "Config/Miscellaneous/OcrConfig.h" #include "MaaUtils/NoWarningCV.hpp" #include "Config/TaskData.h" @@ -10,6 +11,26 @@ #include "Vision/MultiMatcher.h" #include "Vision/OCRer.h" +size_t asst::CreditShopImageAnalyzer::match_required_index( + const std::string& text, + const std::vector& required) +{ + if (text.empty()) { + return required.size(); + } + + auto& ocr_config = OcrConfig::get_instance(); + const std::string equ_text = ocr_config.process_equivalence_class(text); + + for (size_t index = 0; index != required.size(); ++index) { + if (equ_text.find(ocr_config.process_equivalence_class(required.at(index))) != std::string::npos) { + return index; + } + } + + return required.size(); +} + void asst::CreditShopImageAnalyzer::set_black_list(std::vector black_list) { Log.info(__FUNCTION__, black_list); @@ -82,30 +103,33 @@ bool asst::CreditShopImageAnalyzer::whether_to_buy_analyze() OCRer ocr_analyzer(m_image); ocr_analyzer.set_roi(name_roi); ocr_analyzer.set_replace(product_name_task_ptr->replace_map); - ocr_analyzer.set_required(m_shopping_list); - if (ocr_analyzer.analyze()) { - // 黑名单模式,有识别结果说明这个商品不买,直接跳过 - if (!m_is_white_list && !m_shopping_list.empty()) { - continue; - } + if (!ocr_analyzer.analyze()) { + continue; } - // 白名单模式,没有识别结果说明这个商品不买,直接跳过 - else if (m_is_white_list) { + + const auto& ocr_result = ocr_analyzer.get_result(); + if (ocr_result.empty()) { + continue; + } + const std::string& name = ocr_result.front().text; + const size_t match_index = match_required_index(name, m_shopping_list); + + // 黑名单模式,命中黑名单商品则跳过;白名单模式,未命中白名单则跳过。 + if ((!m_is_white_list && !m_shopping_list.empty() && match_index != m_shopping_list.size()) || + (m_is_white_list && match_index == m_shopping_list.size())) { continue; } #ifdef ASST_DEBUG cv::rectangle(m_image_draw, make_rect(commodity), cv::Scalar(0, 0, 255), 2); #endif - const std::string& name = - ocr_analyzer.get_result().empty() ? std::string() : ocr_analyzer.get_result().front().text; Log.info("need to buy", name); - m_need_to_buy.emplace_back(commodity, name); + m_need_to_buy.emplace_back(commodity, 0.0, name); } if (m_is_white_list) { - std::ranges::sort(m_need_to_buy, std::less {}, [&](const auto& pair) { - return std::ranges::find(m_shopping_list, pair.second); + std::ranges::sort(m_need_to_buy, std::less {}, [&](const TextRect& commodity) { + return match_required_index(commodity.text, m_shopping_list); }); } @@ -118,12 +142,18 @@ bool asst::CreditShopImageAnalyzer::sold_out_analyze() Matcher sold_out_analyzer(m_image); sold_out_analyzer.set_task_info("CreditShop-SoldOut"); - for (const auto& commodity : m_need_to_buy | std::views::keys) { - sold_out_analyzer.set_roi(commodity); + for (const TextRect& commodity : m_need_to_buy) { + sold_out_analyzer.set_roi(commodity.rect); if (sold_out_analyzer.analyze()) { #ifdef ASST_DEBUG - cv::rectangle(m_image_draw, make_rect(commodity), cv::Scalar(0, 0, 255)); - cv::putText(m_image_draw, "Sold Out", cv::Point(commodity.x, commodity.y), 1, 2, cv::Scalar(255, 0, 0)); + cv::rectangle(m_image_draw, make_rect(commodity.rect), cv::Scalar(0, 0, 255)); + cv::putText( + m_image_draw, + "Sold Out", + cv::Point(commodity.rect.x, commodity.rect.y), + 1, + 2, + cv::Scalar(255, 0, 0)); #endif // ASST_DEBUG // 如果识别到了售罄,那这个商品就不用买了,跳过 diff --git a/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.h b/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.h index ca15e7e3f2..23e7a70f8e 100644 --- a/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.h +++ b/src/MaaCore/Vision/Miscellaneous/CreditShopImageAnalyzer.h @@ -14,7 +14,7 @@ public: void set_black_list(std::vector black_list); void set_white_list(std::vector white_list); - const std::vector& get_result() const noexcept { return m_result; } + const std::vector& get_result() const noexcept { return m_result; } private: // 该分析器不支持外部设置ROI @@ -23,9 +23,13 @@ private: bool whether_to_buy_analyze(); bool sold_out_analyze(); + static size_t match_required_index( + const std::string& text, + const std::vector& required); + std::vector m_commodities; - std::vector> m_need_to_buy; - std::vector m_result; + std::vector m_need_to_buy; + std::vector m_result; std::vector m_shopping_list; bool m_is_white_list = false;