diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index ee5adcff4..085a907f4 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -1302,6 +1302,8 @@ file(GLOB MAIN_SOURCES Source/PokemonSV/Inference/Dialogs/PokemonSV_GradientArrowDetector.h Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterJobsDetector.cpp Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterJobsDetector.h + Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.cpp + Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.h Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterPrizeReader.cpp Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterPrizeReader.h Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.cpp diff --git a/SerialPrograms/SerialPrograms.pro b/SerialPrograms/SerialPrograms.pro index a5436e6d2..7a010444d 100644 --- a/SerialPrograms/SerialPrograms.pro +++ b/SerialPrograms/SerialPrograms.pro @@ -651,6 +651,7 @@ SOURCES += \ Source/PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.cpp \ Source/PokemonSV/Inference/Dialogs/PokemonSV_GradientArrowDetector.cpp \ Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterJobsDetector.cpp \ + Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.cpp \ Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterPrizeReader.cpp \ Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.cpp \ Source/PokemonSV/Inference/Map/PokemonSV_MapDetector.cpp \ @@ -1725,6 +1726,7 @@ HEADERS += \ Source/PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.h \ Source/PokemonSV/Inference/Dialogs/PokemonSV_GradientArrowDetector.h \ Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterJobsDetector.h \ + Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.h \ Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterPrizeReader.h \ Source/PokemonSV/Inference/Map/PokemonSV_FastTravelDetector.h \ Source/PokemonSV/Inference/Map/PokemonSV_MapDetector.h \ diff --git a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp index fd699898b..52199494a 100644 --- a/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp +++ b/SerialPrograms/Source/NintendoSwitch/DevPrograms/TestProgramSwitch.cpp @@ -117,6 +117,7 @@ #include "NintendoSwitch/Inference/NintendoSwitch_DateReader.h" #include "PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterPrizeReader.h" #include "PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterJobsDetector.h" +#include "PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.h" #include "PokemonSwSh/Inference/PokemonSwSh_IvJudgeReader.h" @@ -287,6 +288,19 @@ void TestProgram::program(MultiSwitchProgramEnvironment& env, CancellableScope& cout << (int)detector.detect_jobs(logger, env.inference_dispatcher(), screen) << endl; #endif +#if 0 + ItemPrinterMaterialDetector detector(COLOR_RED, LANGUAGE); + detector.make_overlays(overlays); + cout << (int)detector.find_happiny_dust_row_index(env.inference_dispatcher(), console, context) << endl; + // cout << (int)detector.detect_material_quantity(env.inference_dispatcher(), console, context, 2) << endl; +#endif + +#if 0 + VideoSnapshot screen = console.video().snapshot(); + ItemPrinterJobsDetector detector(COLOR_RED); + cout << (int)detector.detect_jobs(logger, env.inference_dispatcher(), screen) << endl; +#endif + #if 0 VideoSnapshot screen = console.video().snapshot(); diff --git a/SerialPrograms/Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.cpp b/SerialPrograms/Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.cpp new file mode 100644 index 000000000..37d37f763 --- /dev/null +++ b/SerialPrograms/Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.cpp @@ -0,0 +1,293 @@ +/* Tournament Prize Jobs Detector + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#include +#include "Common/Cpp/Concurrency/SpinLock.h" +#include "Common/Cpp/Concurrency/AsyncDispatcher.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTools/ImageFilter.h" +#include "CommonFramework/OCR/OCR_NumberReader.h" +#include "CommonFramework/Tools/ConsoleHandle.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "PokemonSV_ItemPrinterMaterialDetector.h" +#include + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonSV{ + + +MaterialNameReader& MaterialNameReader::instance(){ + static MaterialNameReader reader; + return reader; +} + +// MaterialNameReader has a very limited dictionary, +// so it can only reliably read the material names with 68% value +// (i.e. Ditto Goo, Happiny Dust, Magby Hair, Beldum Claw) +MaterialNameReader::MaterialNameReader() + : SmallDictionaryMatcher("PokemonSV/ItemPrinterMaterialOCR.json") +{} + +OCR::StringMatchResult MaterialNameReader::read_substring( + Logger& logger, + Language language, + const ImageViewRGB32& image, + const std::vector& text_color_ranges, + double min_text_ratio, double max_text_ratio +) const{ + return match_substring_from_image_multifiltered( + &logger, language, image, text_color_ranges, + MAX_LOG10P, MAX_LOG10P_SPREAD, min_text_ratio, max_text_ratio + ); +} + + +ItemPrinterMaterialDetector::ItemPrinterMaterialDetector(Color color, Language language) + : m_color(color) + , m_language(language) + , m_box_mat_value(Material_Boxes(ImageFloatBox(0.39, 0.176758, 0.025, 0.050))) + , m_box_mat_quantity(Material_Boxes(ImageFloatBox(0.485, 0.176758, 0.037, 0.050))) + , m_box_mat_name(Material_Boxes(ImageFloatBox(0.090, 0.176758, 0.275, 0.050))) +{} + +std::array ItemPrinterMaterialDetector::Material_Boxes(ImageFloatBox initial_box){ + std::array material_boxes; + double x = initial_box.x; + double width = initial_box.width; + double height = initial_box.height; + double initial_y = initial_box.y; + double y_spacing = 0.074219; + for (size_t i = 0; i < 10; i++){ + double y = initial_y + i*y_spacing; + material_boxes[i] = ImageFloatBox(x, y, width, height); + } + return material_boxes; +} + +void ItemPrinterMaterialDetector::make_overlays(VideoOverlaySet& items) const{ + for (size_t i = 0; i < 10; i++){ + items.add(m_color, m_box_mat_value[i]); + items.add(m_color, m_box_mat_quantity[i]); + items.add(m_color, m_box_mat_name[i]); + } +} + +int16_t ItemPrinterMaterialDetector::read_number( + Logger& logger, AsyncDispatcher& dispatcher, + const ImageViewRGB32& screen, const ImageFloatBox& box +) const{ + int16_t number_result_white_text = read_number_black_or_white_text(logger, dispatcher, screen, box, true); + if (number_result_white_text == -1){ + // try looking for black text + return read_number_black_or_white_text(logger, dispatcher, screen, box, false); + }else{ + return number_result_white_text; + } + +} + +int16_t ItemPrinterMaterialDetector::read_number_black_or_white_text( + Logger& logger, AsyncDispatcher& dispatcher, + const ImageViewRGB32& screen, const ImageFloatBox& box, + bool is_white_text +) const{ + ImageViewRGB32 cropped = extract_box_reference(screen, box); + + std::vector filters; + if (is_white_text){ + filters = + { + // white text filters + {0xff808080, 0xffffffff, true}, + {0xff909090, 0xffffffff, true}, + {0xffa0a0a0, 0xffffffff, true}, + {0xffb0b0b0, 0xffffffff, true}, + {0xffc0c0c0, 0xffffffff, true}, + {0xffd0d0d0, 0xffffffff, true}, + {0xffe0e0e0, 0xffffffff, true}, + {0xfff0f0f0, 0xffffffff, true}, + }; + }else{ + filters = + { + // black text filters + // {0xff000000, 0xff101010, true}, + // {0xff000000, 0xff202020, true}, + {0xff000000, 0xff303030, true}, + {0xff000000, 0xff404040, true}, + {0xff000000, 0xff505050, true}, + {0xff000000, 0xff606060, true}, + {0xff000000, 0xff707070, true}, + {0xff000000, 0xff808080, true}, + }; + } + + std::vector> filtered = to_blackwhite_rgb32_range( + cropped, + filters + ); + + SpinLock lock; + std::map candidates; + std::vector> tasks(filtered.size()); + // for (size_t c = 0; c < filtered.size(); c++){ + // filtered[c].first.save("DebugDumps/test-" + std::to_string(c) + ".png"); + // } + for (size_t c = 0; c < filtered.size(); c++){ + tasks[c] = dispatcher.dispatch([&, c]{ + int num = OCR::read_number(logger, filtered[c].first); + WriteSpinLock lg(lock); + candidates[(int16_t)num]++; + }); + } + + // Wait for everything. + for (auto& task : tasks){ + task->wait_and_rethrow_exceptions(); + } + + std::pair best; + std::pair second_best; + for (const auto& item : candidates){ + logger.log("Candidate OCR: " + std::to_string(item.first) + "; x" + std::to_string(item.second)); + if (item.second >= best.second){ + second_best = best; + best = item; + } + // std::cout << "Best: " << std::to_string(best.first) << "; x" << std::to_string(best.second) << std::endl; + // std::cout << "Second Best: " << std::to_string(second_best.first) << "; x" << std::to_string(second_best.second) << std::endl; + } + + if (best.second > second_best.second + 3){ + return best.first; + }else{ + logger.log("Ambiguous or multiple results with normal number OCR. Use Waterfill number OCR."); + if(is_white_text){ + return (int16_t)OCR::read_number_waterfill(logger, cropped, 0xff808080, 0xffffffff); + }else{ + return (int16_t)OCR::read_number_waterfill(logger, cropped, 0xff000000, 0xff808080); + } + } + +} + +// return row number where Happiny dust is located on screen +// keep pressing DPAD_RIGHT until Happiny dust is on screen +// check each row on the screen for Happiny Dust +int8_t ItemPrinterMaterialDetector::find_happiny_dust_row_index( + AsyncDispatcher& dispatcher, + ConsoleHandle& console, BotBaseContext& context +) const{ + int8_t value_68_row_index; + for (size_t c = 0; c < 30; c++){ + context.wait_for_all_requests(); + std::vector value_68_row_index_list = find_material_value_row_index(dispatcher, console, context, 68); + for (size_t i = 0; i < value_68_row_index_list.size(); i++){ + value_68_row_index = value_68_row_index_list[i]; + if (detect_material_name(console, context, value_68_row_index) == "happiny-dust"){ + // found screen and row number with Happiny dust. + // std::cout << "Happiny dust found. Row number: " << std::to_string(value_68_row_index) << std::endl; + return value_68_row_index; + } + } + + // keep searching for Happiny dust + pbf_press_dpad(context, DPAD_RIGHT, 20, 30); + } + + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + console, + "Failed to find Happiny dust after 10 tries." + ); + +} + +// detects the material name at the given row_index +// MaterialNameReader has a very limited dictionary, +// so it can only reliably read the material names with 68% value +// (i.e. Ditto Goo, Happiny Dust, Magby Hair, Beldum Claw) +std::string ItemPrinterMaterialDetector::detect_material_name( + ConsoleHandle& console, + BotBaseContext& context, + int8_t row_index +) const{ + VideoSnapshot snapshot = console.video().snapshot(); + ImageFloatBox material_name_box = m_box_mat_name[row_index]; + ImageViewRGB32 material_name_image = extract_box_reference(snapshot, material_name_box); + const auto ocr_result = MaterialNameReader::instance().read_substring( + console, m_language, + material_name_image, OCR::BLACK_OR_WHITE_TEXT_FILTERS() + ); + + std::multimap results; + if (!ocr_result.results.empty()){ + for (const auto& result : ocr_result.results){ + results.emplace(result.first, result.second); + } + } + + if (results.empty()){ + return ""; + } + + if (results.size() > 1){ + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + console, + "ItemPrinterMaterialDetector::detect_material_name(): Unable to read selected item. Ambiguous or multiple results." + ); + } + + return results.begin()->second.token; +} + +// return vector of row index(es) that matches given material_value. +std::vector ItemPrinterMaterialDetector::find_material_value_row_index( + AsyncDispatcher& dispatcher, + ConsoleHandle& console, + BotBaseContext& context, + int16_t material_value +) const{ + context.wait_for_all_requests(); + VideoSnapshot snapshot = console.video().snapshot(); + int8_t total_rows = 10; + std::vector row_indexes; + for (int8_t row_index = 0; row_index < total_rows; row_index++){ + int16_t value = read_number(console, dispatcher, snapshot, m_box_mat_value[row_index]); + if (value == material_value){ + row_indexes.push_back(row_index); + } + } + + return row_indexes; + +} + +// detect the quantity of material at the given row number +int16_t ItemPrinterMaterialDetector::detect_material_quantity( + AsyncDispatcher& dispatcher, + ConsoleHandle& console, + BotBaseContext& context, + int8_t row_index +) const{ + context.wait_for_all_requests(); + VideoSnapshot snapshot = console.video().snapshot(); + int16_t value = read_number(console, dispatcher, snapshot, m_box_mat_quantity[row_index]); + return value; +} + + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.h b/SerialPrograms/Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.h new file mode 100644 index 000000000..64ac5326d --- /dev/null +++ b/SerialPrograms/Source/PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.h @@ -0,0 +1,104 @@ +/* Item Printer Jobs Detector + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#ifndef PokemonAutomation_PokemonSV_ItemPrinterMaterialDetector_H +#define PokemonAutomation_PokemonSV_ItemPrinterMaterialDetector_H + +#include "Common/Cpp/Color.h" +#include "CommonFramework/Language.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Inference/VisualDetector.h" +#include "CommonFramework/OCR/OCR_SmallDictionaryMatcher.h" +#include + +namespace PokemonAutomation{ + class Logger; + class AsyncDispatcher; + class ConsoleHandle; + class BotBaseContext; +namespace NintendoSwitch{ +namespace PokemonSV{ + + +class MaterialNameReader : public OCR::SmallDictionaryMatcher{ + static constexpr double MAX_LOG10P = -1.40; + static constexpr double MAX_LOG10P_SPREAD = 0.50; + +public: + MaterialNameReader(); + + static MaterialNameReader& instance(); + + OCR::StringMatchResult read_substring( + Logger& logger, + Language language, + const ImageViewRGB32& image, + const std::vector& text_color_ranges, + double min_text_ratio = 0.01, double max_text_ratio = 0.50 + ) const; +}; + +class ItemPrinterMaterialDetector{ +public: + ItemPrinterMaterialDetector(Color color, Language language); + + void make_overlays(VideoOverlaySet& items) const; + + std::array Material_Boxes(ImageFloatBox initial_box); + + int8_t find_happiny_dust_row_index( + AsyncDispatcher& dispatcher, + ConsoleHandle& console, BotBaseContext& context + ) const; + + std::vector find_material_value_row_index( + AsyncDispatcher& dispatcher, + ConsoleHandle& console, + BotBaseContext& context, + int16_t material_value + ) const; + + int16_t detect_material_quantity( + AsyncDispatcher& dispatcher, + ConsoleHandle& console, + BotBaseContext& context, + int8_t row_index + ) const; + + std::string detect_material_name( + ConsoleHandle& console, + BotBaseContext& context, + int8_t row_index + ) const; + + +private: + int16_t read_number( + Logger& logger, AsyncDispatcher& dispatcher, + const ImageViewRGB32& screen, const ImageFloatBox& box + ) const; + + int16_t read_number_black_or_white_text( + Logger& logger, AsyncDispatcher& dispatcher, + const ImageViewRGB32& screen, const ImageFloatBox& box, + bool is_white_text + ) const; + + +private: + Color m_color; + Language m_language; + std::array m_box_mat_value; + std::array m_box_mat_quantity; + std::array m_box_mat_name; +}; + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonSV/Programs/ItemPrinter/PokemonSV_ItemPrinterRNG.cpp b/SerialPrograms/Source/PokemonSV/Programs/ItemPrinter/PokemonSV_ItemPrinterRNG.cpp index cdd394fdb..5aa96ffa7 100644 --- a/SerialPrograms/Source/PokemonSV/Programs/ItemPrinter/PokemonSV_ItemPrinterRNG.cpp +++ b/SerialPrograms/Source/PokemonSV/Programs/ItemPrinter/PokemonSV_ItemPrinterRNG.cpp @@ -26,6 +26,7 @@ #include "PokemonSV/Inference/PokemonSV_WhiteButtonDetector.h" #include "PokemonSV/Inference/Dialogs/PokemonSV_DialogDetector.h" #include "PokemonSV/Inference/Overworld/PokemonSV_OverworldDetector.h" +#include "PokemonSV/Inference/ItemPrinter/PokemonSV_ItemPrinterMaterialDetector.h" //#include "PokemonSV/Programs/PokemonSV_GameEntry.h" #include "PokemonSV/Programs/Farming/PokemonSV_MaterialFarmerTools.h" #include "PokemonSV/Programs/PokemonSV_Navigation.h" @@ -89,6 +90,7 @@ std::unique_ptr ItemPrinterRNG_Descriptor::make_stats() const{ ItemPrinterRNG::~ItemPrinterRNG(){ MATERIAL_FARMER_OPTIONS.remove_listener(*this); + MATERIAL_FARMER_TRIGGER.remove_listener(*this); TABLE0.remove_listener(*this); // AUTO_MATERIAL_FARMING.remove_listener(*this); } @@ -156,12 +158,32 @@ ItemPrinterRNG::ItemPrinterRNG() // 3 // ) // , MATERIAL_FARMER_DISABLED_EXPLANATION("") - , MATERIAL_FARMER_JOBS_PERIOD( + , MATERIAL_FARMER_TRIGGER( + "Trigger to start material farmer:
", + { + {MaterialFarmerTrigger::FIXED_NUM_PRINT_JOBS, "fixed", "Start Material Farmer when done a certain number of print jobs."}, + {MaterialFarmerTrigger::MINIMUM_HAPPINY_DUST, "happiny-dust", "Start Material Farmer when Happiny Dust is less than a certain number."}, + }, + LockMode::UNLOCK_WHILE_RUNNING, + MaterialFarmerTrigger::FIXED_NUM_PRINT_JOBS + ) + , MATERIAL_FARMER_FIXED_NUM_JOBS( "Print Jobs per Material Farming Session:
" "Run the material farmer once for this many jobs printed.", LockMode::UNLOCK_WHILE_RUNNING, 500, 20, 650 ) + , MIN_HAPPINY_DUST( + "Minimum Happiny Dust:
" + "Run the material farmer before the number of Happiny Dust drops " + "below this number.
" + "This ensures no other material drops below this number. " + "If a material starts below this threshold, it remains there.
" + "Changes to this number only take place after returning to " + "the item printer, after material farming.", + LockMode::UNLOCK_WHILE_RUNNING, 400, + 1, 999 + ) , MATERIAL_FARMER_OPTIONS( true, false, &LANGUAGE, @@ -200,7 +222,9 @@ ItemPrinterRNG::ItemPrinterRNG() if (PreloadSettings::instance().DEVELOPER_MODE){ // PA_ADD_OPTION(MATERIAL_FARMER_DISABLED_EXPLANATION); - PA_ADD_OPTION(MATERIAL_FARMER_JOBS_PERIOD); + PA_ADD_OPTION(MATERIAL_FARMER_TRIGGER); + PA_ADD_OPTION(MATERIAL_FARMER_FIXED_NUM_JOBS); + PA_ADD_OPTION(MIN_HAPPINY_DUST); PA_ADD_OPTION(MATERIAL_FARMER_OPTIONS); } if (PreloadSettings::instance().DEVELOPER_MODE){ @@ -213,6 +237,7 @@ ItemPrinterRNG::ItemPrinterRNG() // AUTO_MATERIAL_FARMING.add_listener(*this); TABLE0.add_listener(*this); MATERIAL_FARMER_OPTIONS.add_listener(*this); + MATERIAL_FARMER_TRIGGER.add_listener(*this); } void ItemPrinterRNG::value_changed(void* object){ @@ -227,8 +252,13 @@ void ItemPrinterRNG::value_changed(void* object){ // MATERIAL_FARMER_DISABLED_EXPLANATION.set_text("
To enable the Material Farmer, enable \"Automatic Material Farming\" above"); // } - MATERIAL_FARMER_JOBS_PERIOD.set_visibility( - MATERIAL_FARMER_OPTIONS.enabled() ? ConfigOptionState::ENABLED : ConfigOptionState::DISABLED + MATERIAL_FARMER_FIXED_NUM_JOBS.set_visibility( + MATERIAL_FARMER_OPTIONS.enabled() && (MATERIAL_FARMER_TRIGGER == MaterialFarmerTrigger::FIXED_NUM_PRINT_JOBS) + ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN + ); + MIN_HAPPINY_DUST.set_visibility( + MATERIAL_FARMER_OPTIONS.enabled() && (MATERIAL_FARMER_TRIGGER == MaterialFarmerTrigger::MINIMUM_HAPPINY_DUST) + ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN ); OVERLAPPING_BONUS_WARNING.set_visibility( overlapping_bonus() ? ConfigOptionState::ENABLED : ConfigOptionState::HIDDEN @@ -574,19 +604,29 @@ void ItemPrinterRNG::run_item_printer_rng( SingleSwitchProgramEnvironment& env, BotBaseContext& context, ItemPrinterRNG_Descriptor::Stats& stats ){ - // For each job that we print, we increment this number. - // Each time we run the material farmer, we decrease this number by MATERIAL_FARMER_JOBS_PERIOD. + // For each job that we print, we increment jobs_counter. + // Each time we run the material farmer, we reset jobs_counter to 0. uint32_t jobs_counter = 0; + bool done_one_last_material_check_before_mat_farming = false; + uint32_t material_farmer_jobs_period = MATERIAL_FARMER_FIXED_NUM_JOBS; + if (MATERIAL_FARMER_TRIGGER == MaterialFarmerTrigger::MINIMUM_HAPPINY_DUST){ + // Check material quantity when: + // - once when first starting the item printer + // - before starting material farming. If still have material, + // can keep using item printer. But this check is only done once, + // until you farm materials again. + // - when back from material farming + + uint32_t num_jobs_with_happiny_dust = calc_num_jobs_with_happiny_dust(env, context); + material_farmer_jobs_period = num_jobs_with_happiny_dust; + } + for (uint32_t c = 0; c < NUM_ITEM_PRINTER_ROUNDS; c++){ send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); std::vector table = TABLE0.snapshot(); for (const ItemPrinterRngRowSnapshot& row : table){ - if (!MATERIAL_FARMER_OPTIONS.enabled()){ - jobs_counter = 0; - } - // Cannot run material farmer between chained prints. if (row.chain){ print_again(env, context, row.jobs); @@ -597,16 +637,43 @@ void ItemPrinterRNG::run_item_printer_rng( run_print_at_date(env, context, row.date, row.jobs); jobs_counter += (uint32_t)row.jobs; - // Material farmer is disabled. if (!MATERIAL_FARMER_OPTIONS.enabled()){ continue; } + //////////////////////////////////////////////////// + // Material farmer is enabled. + // Check number of print jobs before triggering material farmer. + //////////////////////////////////////////////////// + + if (MATERIAL_FARMER_TRIGGER == MaterialFarmerTrigger::FIXED_NUM_PRINT_JOBS){ + material_farmer_jobs_period = MATERIAL_FARMER_FIXED_NUM_JOBS; + } + env.console.log( + "Print job counter: " + + std::to_string(jobs_counter) + + "/" + + std::to_string(material_farmer_jobs_period) + ); + // Not ready to run material farmer yet. - uint16_t period = MATERIAL_FARMER_JOBS_PERIOD; - if (jobs_counter < period){ + if (jobs_counter < material_farmer_jobs_period){ continue; } + + if (MATERIAL_FARMER_TRIGGER == MaterialFarmerTrigger::MINIMUM_HAPPINY_DUST + && !done_one_last_material_check_before_mat_farming + ){ + // one more material quantity check before material farming + // if still have material, keep using item printer + uint32_t num_jobs_with_happiny_dust = calc_num_jobs_with_happiny_dust(env, context); + material_farmer_jobs_period = num_jobs_with_happiny_dust; + jobs_counter = 0; + done_one_last_material_check_before_mat_farming = true; + if (material_farmer_jobs_period > 0){ + continue; + } + } // Run the material farmer. press_Bs_to_back_to_overworld(env.program_info(), env.console, context); @@ -620,7 +687,15 @@ void ItemPrinterRNG::run_item_printer_rng( } move_from_material_farming_to_item_printer(env, context); - jobs_counter -= period; + // Recheck number of Happiny Dust after returning from Material Farming, + // prior to restarting Item printing + if (MATERIAL_FARMER_TRIGGER == MaterialFarmerTrigger::MINIMUM_HAPPINY_DUST){ + uint32_t num_jobs_with_happiny_dust = calc_num_jobs_with_happiny_dust(env, context); + material_farmer_jobs_period = num_jobs_with_happiny_dust; + done_one_last_material_check_before_mat_farming = false; + } + + jobs_counter = 0; } // run_print_at_date(env, context, DATE0, 1); // run_print_at_date(env, context, DATE1, 10); @@ -629,6 +704,86 @@ void ItemPrinterRNG::run_item_printer_rng( } } +uint32_t ItemPrinterRNG::calc_num_jobs_with_happiny_dust( + SingleSwitchProgramEnvironment& env, BotBaseContext& context +){ + uint32_t num_happiny_dust = check_num_happiny_dust(env, context); + + // give a buffer of 50, for a margin of safety. signed int to handle negative numbers + int32_t num_happiny_dust_can_use = num_happiny_dust - MIN_HAPPINY_DUST - 50; + num_happiny_dust_can_use = num_happiny_dust_can_use < 0 ? 0 : num_happiny_dust_can_use; + + // assume 62% value for Happiny Dust to account for item printer wasteage. + uint32_t num_print_jobs = num_happiny_dust_can_use * 0.62; // truncate the float back to int + env.console.log("Number of Happiny Dust we have: " + std::to_string(num_happiny_dust)); + env.console.log("Number of Happiny Dust we can use (with some safety margins): " + std::to_string(num_happiny_dust_can_use)); + env.console.log("Number of print jobs we can do before material farming: " + std::to_string(num_print_jobs)); + return num_print_jobs; +} + +uint32_t ItemPrinterRNG::check_num_happiny_dust( + SingleSwitchProgramEnvironment& env, BotBaseContext& context +){ + ItemPrinterRNG_Descriptor::Stats& stats = env.current_stats(); + env.log("Check how much Happiny Dust we have."); + uint32_t num_happiny_dust; + while (true){ + context.wait_for_all_requests(); + + OverworldWatcher overworld(COLOR_BLUE); + AdvanceDialogWatcher dialog(COLOR_RED); + PromptDialogWatcher prompt(COLOR_GREEN); + DateChangeWatcher date_reader; + WhiteButtonWatcher material(COLOR_GREEN, WhiteButton::ButtonX, {0.63, 0.93, 0.17, 0.06}); + int ret = wait_until( + env.console, context, std::chrono::seconds(120), + { + overworld, + dialog, + prompt, + material, + } + ); + switch (ret){ + case 0: + env.log("Detected overworld... Entering item printer."); + pbf_press_button(context, BUTTON_A, 20, 30); + continue; + + case 1: + env.log("Detected advance dialog."); + pbf_press_button(context, BUTTON_B, 20, 30); + continue; + + case 2:{ + env.log("Detected prompt dialog. Entering item printer."); + pbf_press_button(context, BUTTON_A, 10, 30); + context.wait_for_all_requests(); + continue; + } + case 3:{ + env.log("Detected material selection."); + ItemPrinterMaterialDetector detector(COLOR_RED, LANGUAGE); + + int8_t happiny_dust_row_num = detector.find_happiny_dust_row_index(env.inference_dispatcher(), env.console, context); + num_happiny_dust = detector.detect_material_quantity(env.inference_dispatcher(), env.console, context, happiny_dust_row_num); + pbf_mash_button(context, BUTTON_B, 100); + return num_happiny_dust; + } + default: + stats.errors++; + env.update_stats(); + throw OperationFailedException( + ErrorReport::SEND_ERROR_REPORT, + env.logger(), + "" + ); + } + } + + return 0; +} + void ItemPrinterRNG::program(SingleSwitchProgramEnvironment& env, BotBaseContext& context){ assert_16_9_720p_min(env.logger(), env.console); ItemPrinterRNG_Descriptor::Stats& stats = env.current_stats(); diff --git a/SerialPrograms/Source/PokemonSV/Programs/ItemPrinter/PokemonSV_ItemPrinterRNG.h b/SerialPrograms/Source/PokemonSV/Programs/ItemPrinter/PokemonSV_ItemPrinterRNG.h index b3f02057f..fae36f11d 100644 --- a/SerialPrograms/Source/PokemonSV/Programs/ItemPrinter/PokemonSV_ItemPrinterRNG.h +++ b/SerialPrograms/Source/PokemonSV/Programs/ItemPrinter/PokemonSV_ItemPrinterRNG.h @@ -73,6 +73,14 @@ class ItemPrinterRNG : public SingleSwitchProgramInstance, public ConfigOption:: const std::array& expected_result ); + uint32_t calc_num_jobs_with_happiny_dust( + SingleSwitchProgramEnvironment& env, BotBaseContext& context + ); + + uint32_t check_num_happiny_dust( + SingleSwitchProgramEnvironment& env, BotBaseContext& context + ); + private: OCR::LanguageOCROption LANGUAGE; SimpleIntegerOption NUM_ITEM_PRINTER_ROUNDS; @@ -91,7 +99,13 @@ class ItemPrinterRNG : public SingleSwitchProgramInstance, public ConfigOption:: // SimpleIntegerOption NUM_ROUNDS_OF_ITEM_PRINTER_TO_MATERIAL_FARM; // StaticTextOption MATERIAL_FARMER_DISABLED_EXPLANATION; - SimpleIntegerOption MATERIAL_FARMER_JOBS_PERIOD; + enum class MaterialFarmerTrigger{ + FIXED_NUM_PRINT_JOBS, + MINIMUM_HAPPINY_DUST, + }; + EnumDropdownOption MATERIAL_FARMER_TRIGGER; + SimpleIntegerOption MATERIAL_FARMER_FIXED_NUM_JOBS; + SimpleIntegerOption MIN_HAPPINY_DUST; MaterialFarmerOptions MATERIAL_FARMER_OPTIONS; BooleanCheckBoxOption ENABLE_SEED_CALC;