diff --git a/SerialPrograms/Source/CommonFramework/ProgramStats/StatsDatabase.cpp b/SerialPrograms/Source/CommonFramework/ProgramStats/StatsDatabase.cpp index 7ecd756b27..aa7125bf00 100644 --- a/SerialPrograms/Source/CommonFramework/ProgramStats/StatsDatabase.cpp +++ b/SerialPrograms/Source/CommonFramework/ProgramStats/StatsDatabase.cpp @@ -37,6 +37,7 @@ const std::map STATS_DATABASE_ALIASES{ {"PokemonSV:StatsResetBloodmoon", "PokemonSV:StatsResetEventBattle"}, {"PokemonLZA:ShinyHunt-Bench", "PokemonLZA:ShinyHunt-BenchSit"}, {"PokemonLZA:BerryBuyer", "PokemonLZA:StallBuyer"}, + {"PokemonFRLG:LuckyEggFarmer", "PokemonFRLG:HeldItemFarmerSafariZone"}, }; diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h index 53924813ae..5784c7d8da 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h @@ -122,6 +122,9 @@ int grass_spin(ConsoleHandle& console, ProControllerContext& context, bool leftr // returns -1 if no encounter is triggered, 0 if a non-shiny is encounter, and 1 if a shiny is encountered int fish_encounter(ConsoleHandle& console, ProControllerContext& context, Seconds timeout = 300s); +// tries to pull up the start menu to check if currently in the overworld +bool in_overworld(ConsoleHandle& console, ProControllerContext& context); + // Automatically catch a Pokemon. // Returns -1 if there are detection issues, 0 if the Pokemon was not caught, and the number of balls thrown if it was caught. int auto_catch( diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index c0c0db53ad..4f1a54b2f8 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -10,8 +10,8 @@ #include "PokemonFRLG_Panels.h" #include "PokemonFRLG_Settings.h" +#include "Programs/Farming/PokemonFRLG_HeldItemFarmer-SafariZone.h" #include "Programs/Farming/PokemonFRLG_ItemDuplication.h" -#include "Programs/Farming/PokemonFRLG_LuckyEggFarmer.h" #include "Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h" #include "Programs/Farming/PokemonFRLG_PickupFarmer.h" #include "Programs/Farming/PokemonFRLG_EvTrainer.h" @@ -59,7 +59,7 @@ std::vector PanelListFactory::make_panels() const{ ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); - ret.emplace_back(make_single_switch_program()); + ret.emplace_back(make_single_switch_program()); ret.emplace_back(make_single_switch_program()); //ret.emplace_back("---- General ----"); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_HeldItemFarmer-SafariZone.cpp similarity index 58% rename from SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp rename to SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_HeldItemFarmer-SafariZone.cpp index eaed11ad64..d4faf39083 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_HeldItemFarmer-SafariZone.cpp @@ -1,10 +1,12 @@ -/* Lucky Egg Farmer +/* Held Item Farmer - Safari Zone * * From: https://github.com/PokemonAutomation/ * */ +#include "CommonFramework/GlobalSettingsPanel.h" #include "Common/Cpp/Options/ButtonOption.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" #include "CommonFramework/ImageTools/ImageBoxes.h" #include "CommonFramework/Language.h" #include "CommonFramework/Notifications/ProgramNotifications.h" @@ -17,63 +19,85 @@ #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_StartMenuDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_BattleSelectionArrowDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_PokedexRegisteredDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_WildEncounterReader.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" -#include "PokemonFRLG_LuckyEggFarmer.h" +#include "PokemonFRLG_HeldItemFarmer-SafariZone.h" +#include "Common/Cpp/Exceptions.h" +#include "PokemonFRLG/Programs/PokemonFRLG_SafariOptimalAction.h" +#include "PokemonFRLG/Programs/PokemonFRLG_BattleMenuNavigation.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -LuckyEggFarmer_Descriptor::LuckyEggFarmer_Descriptor() +HeldItemFarmerSafariZone_Descriptor::HeldItemFarmerSafariZone_Descriptor() : SingleSwitchProgramDescriptor( - "PokemonFRLG:LuckyEggFarmer", - Pokemon::STRING_POKEMON + " FRLG", "Lucky Egg Farmer", - "Programs/PokemonFRLG/LuckyEggFarmer.html", - "Farm the Lucky Egg from Chansey.", + "PokemonFRLG:HeldItemFarmerSafariZone", + Pokemon::STRING_POKEMON + " FRLG", "Held Item Farmer - Safari Zone", + "Programs/PokemonFRLG/HeldItemFarmer-SafariZone.html", + "Farm held items from Chansey and Dragonair in the Safari Zone.", ProgramControllerClass::StandardController_RequiresPrecision, FeedbackType::REQUIRED, AllowCommandsWhenRunning::DISABLE_COMMANDS ) {} -struct LuckyEggFarmer_Descriptor::Stats : public StatsTracker { +struct HeldItemFarmerSafariZone_Descriptor::Stats : public StatsTracker { public: Stats() : encounters(m_stats["Encounters"]) - , chanseys_found(m_stats["Chanseys Found"]) - , chanseys_caught(m_stats["Chanseys Caught"]) - , eggs(m_stats["Lucky Eggs"]) + , target_pokemon_found(m_stats["Target Pokemon Found"]) + , target_pokemon_caught(m_stats["Target Pokemon Caught"]) + , items(m_stats["Items"]) , shinies(m_stats["Shinies"]) , errors(m_stats["Errors"]) { m_display_order.emplace_back("Encounters"); - m_display_order.emplace_back("Chanseys Found"); - m_display_order.emplace_back("Chanseys Caught"); - m_display_order.emplace_back("Lucky Eggs"); + m_display_order.emplace_back("Target Pokemon Found"); + m_display_order.emplace_back("Target Pokemon Caught"); + m_display_order.emplace_back("Items"); m_display_order.emplace_back("Shinies", HIDDEN_IF_ZERO); m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); -// m_aliases["Chanseys Caught"] = "Caught"; - m_aliases["Lucky Eggs Found"] = "Lucky Eggs"; + m_aliases["Lucky Eggs Found"] = "Items"; + m_aliases["Lucky Eggs"] = "Items"; + m_aliases["Chanseys Found"] = "Target Pokemon Found"; + m_aliases["Chanseys Caught"] = "Target Pokemon Caught"; } std::atomic& encounters; - std::atomic& chanseys_found; - std::atomic& chanseys_caught; - std::atomic& eggs; + std::atomic& target_pokemon_found; + std::atomic& target_pokemon_caught; + std::atomic& items; std::atomic& shinies; std::atomic& errors; }; -std::unique_ptr LuckyEggFarmer_Descriptor::make_stats() const{ +std::unique_ptr HeldItemFarmerSafariZone_Descriptor::make_stats() const{ return std::unique_ptr(new Stats()); } -LuckyEggFarmer::LuckyEggFarmer() - : LANGUAGE( +EnumDropdownDatabase HeldItemFarmerSafariZone::item_to_farm_options(){ + EnumDropdownDatabase options{ + {ItemToFarm::LUCKY_EGG, "Lucky Egg", "Farm Chansey for Lucky Eggs."}, + {ItemToFarm::DRAGON_FANG, "Dragon Fang", "Farm Dragonair for Dragon Fangs."} + }; + + return options; +} + +HeldItemFarmerSafariZone::HeldItemFarmerSafariZone() + : ITEM_TO_FARM( + "Item to Farm:", + item_to_farm_options(), + LockMode::LOCK_WHILE_RUNNING, + ItemToFarm::LUCKY_EGG + ) + , LANGUAGE( "Game Language:", { Language::English, @@ -93,8 +117,8 @@ LuckyEggFarmer::LuckyEggFarmer() ) , GO_HOME_WHEN_DONE(false) , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600)) - , NOTIFICATION_LUCKY_EGG( - "Lucky Egg Found", + , NOTIFICATION_HELD_ITEM( + "Held Item Found", true, true, ImageAttachmentMode::JPG, { "Notifs", "Showcase" } ) @@ -105,12 +129,13 @@ LuckyEggFarmer::LuckyEggFarmer() ) , NOTIFICATIONS({ &NOTIFICATION_STATUS_UPDATE, - &NOTIFICATION_LUCKY_EGG, + &NOTIFICATION_HELD_ITEM, &NOTIFICATION_SHINY, &NOTIFICATION_PROGRAM_FINISH, &NOTIFICATION_ERROR_FATAL, }) { + PA_ADD_OPTION(ITEM_TO_FARM); PA_ADD_OPTION(LANGUAGE); PA_ADD_OPTION(STOP_AFTER_CURRENT); PA_ADD_OPTION(TAKE_VIDEO); @@ -119,7 +144,7 @@ LuckyEggFarmer::LuckyEggFarmer() PA_ADD_OPTION(NOTIFICATIONS); } -bool LuckyEggFarmer::navigate_to_chansey(ConsoleHandle& console, ProControllerContext& context){ +bool HeldItemFarmerSafariZone::navigate_to_chansey(ConsoleHandle& console, ProControllerContext& context){ BlackScreenWatcher zone_exit(COLOR_RED); int ret = run_until( @@ -257,7 +282,14 @@ bool LuckyEggFarmer::navigate_to_chansey(ConsoleHandle& console, ProControllerCo return true; } -void LuckyEggFarmer::swap_lead_pokemon(ConsoleHandle& console, ProControllerContext& context){ +void HeldItemFarmerSafariZone::navigate_to_dragonair(ConsoleHandle& console, ProControllerContext& context){ + ssf_press_button(context, BUTTON_B, 0ms, 2848ms); + pbf_press_dpad(context, DPAD_UP, 1315ms, 0ms); + pbf_press_dpad(context, DPAD_RIGHT, 755ms, 0ms); + pbf_press_dpad(context, DPAD_UP, 580ms, 0ms); +} + +void HeldItemFarmerSafariZone::swap_lead_pokemon(ConsoleHandle& console, ProControllerContext& context){ open_party_menu_from_overworld(console, context, StartMenuContext::SAFARI_ZONE); pbf_press_button(context, BUTTON_A, 250ms, 100ms); pbf_press_dpad(context, DPAD_UP, 250ms, 100ms); @@ -271,7 +303,7 @@ void LuckyEggFarmer::swap_lead_pokemon(ConsoleHandle& console, ProControllerCont context.wait_for_all_requests(); } -bool LuckyEggFarmer::find_encounter(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ +int HeldItemFarmerSafariZone::find_encounter_grass(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ ssf_press_button(context, BUTTON_B, 0ms, 400ms); pbf_press_dpad(context, DPAD_RIGHT, 400ms, 0ms); pbf_wait(context, 100ms); @@ -302,15 +334,15 @@ bool LuckyEggFarmer::find_encounter(SingleSwitchProgramEnvironment& env, ProCont case 0: case 1: env.log("Battle entered."); - return true; + return handle_encounter(env.console, context, true) ? 1 : 0; case 2: env.log("Out of steps dialog detected. Resetting..."); - return false; + return -1; } } } -bool LuckyEggFarmer::is_chansey(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ +bool HeldItemFarmerSafariZone::is_chansey(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ std::set subset = { "nidoran-f", "nidoran-m", @@ -335,89 +367,29 @@ bool LuckyEggFarmer::is_chansey(SingleSwitchProgramEnvironment& env, ProControll return encounter.name == "chansey"; } -bool LuckyEggFarmer::attempt_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int& balls_left){ - //TODO: Optimal bait/ball throwing - - while (true){ - BattleSelectionArrowWatcher nickname_question_arrow( - COLOR_RED, - &env.console.overlay(), - BattleConfirmationOption::YES - ); - - BattleSelectionArrowWatcher battle_arrow( - COLOR_RED, - &env.console.overlay(), - SafariBattleMenuOption::BALL - ); - - BlackScreenWatcher battle_end(COLOR_RED); - - AdvanceBattleDialogWatcher advance_battle_dialog(COLOR_RED); - - PokedexRegisteredWatcher pokedex_registered(COLOR_RED, &env.console.overlay()); - - WhiteDialogWatcher in_safari_zone_building(COLOR_RED); - - WallClock start = current_time(); - while (true){ - if (current_time() - start > std::chrono::seconds(20)){ - env.log("No battle activity detected for 20 seconds. Assuming battle ended and in the overworld."); - - //Check for safari zone building dialog? - - return false; - } - - int ret = wait_until( - env.console, context, - std::chrono::milliseconds(2000), - { nickname_question_arrow, battle_arrow, battle_end, advance_battle_dialog } - ); +bool HeldItemFarmerSafariZone::is_dragonair(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + std::set subset = { + "goldeen", + "seaking", + "dratini", + "psyduck", + "slowpoke", + "dragonair" + }; - context.wait_for_all_requests(); + WildEncounterReader reader(COLOR_RED); + VideoOverlaySet overlays(env.console.overlay()); + reader.make_overlays(overlays); - if (ret == 0 || ret == 3){ - env.log("Caught a Chansey!"); - - while (true){ - int ret2 = wait_until( - env.console, context, - std::chrono::milliseconds(2000), - { nickname_question_arrow, advance_battle_dialog, pokedex_registered, in_safari_zone_building } - ); - - if (ret2 == 0 || ret2 == 3){ - pbf_mash_button(context, BUTTON_B, 2000ms); - context.wait_for_all_requests(); - break; - } - else if (ret2 == 1 || ret2 == 2){ - pbf_press_button(context, BUTTON_B, 200ms, 0ms); - context.wait_for_all_requests(); - } - } + env.log("Reading name..."); + VideoSnapshot screen = env.console.video().snapshot(); + PokemonFRLG_WildEncounter encounter = reader.read_encounter(env.logger(), LANGUAGE, screen, subset); + env.log("Name: " + encounter.name); - pbf_mash_button(context, BUTTON_B, 1500ms); - context.wait_for_all_requests(); - return true; - }else if (ret == 1){ - balls_left--; - env.log("Detected battle arrow. Balls left: " + std::to_string(balls_left)); - pbf_press_button(context, BUTTON_A, 200ms, 200ms); - context.wait_for_all_requests(); - break; - }else if (ret == 2){ - env.log("Failed to catch Chansey."); - pbf_wait(context, 1000ms); - context.wait_for_all_requests(); - return false; - } - } - } + return encounter.name == "dragonair"; } -bool LuckyEggFarmer::check_for_lucky_egg(ConsoleHandle& console, ProControllerContext& context, bool returned_to_building){ +bool HeldItemFarmerSafariZone::check_for_held_item(ConsoleHandle& console, ProControllerContext& context, bool returned_to_building, int party_count){ if (returned_to_building){ open_party_menu_from_overworld(console, context, StartMenuContext::STANDARD); } @@ -425,7 +397,7 @@ bool LuckyEggFarmer::check_for_lucky_egg(ConsoleHandle& console, ProControllerCo open_party_menu_from_overworld(console, context, StartMenuContext::SAFARI_ZONE); } - PartyHeldItemDetector held_item_detector(COLOR_RED, &console.overlay(), ImageFloatBox(0.432, 0.3, 0.030, 0.485)); + PartyHeldItemDetector held_item_detector(COLOR_RED, &console.overlay(), static_cast(party_count - 1)); if (held_item_detector.detect(console.video().snapshot())){ return true; } @@ -433,21 +405,27 @@ bool LuckyEggFarmer::check_for_lucky_egg(ConsoleHandle& console, ProControllerCo return false; } -bool LuckyEggFarmer::run_safari_zone(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ - LuckyEggFarmer_Descriptor::Stats& stats = env.current_stats(); +bool HeldItemFarmerSafariZone::run_safari_zone(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int party_count){ + HeldItemFarmerSafariZone_Descriptor::Stats& stats = env.current_stats(); - int chansey_count = 0; int balls_left = 30; - while (chansey_count < 4){ + while (party_count < 6){ send_program_status_notification(env, NOTIFICATION_STATUS_UPDATE); - if (!find_encounter(env, context)){ - return false; + int encounter_result = -1; + if (ITEM_TO_FARM == ItemToFarm::LUCKY_EGG){ + encounter_result = find_encounter_grass(env, context); + }else{ + encounter_result = fish_encounter(env.console, context); } - bool encounter_shiny = handle_encounter(env.console, context, true); - stats.encounters++; - if (encounter_shiny){ + switch (encounter_result){ + case 0: + stats.encounters++; + env.update_stats(); + break; + case 1: + stats.encounters++; stats.shinies++; env.update_stats(); send_program_notification( @@ -463,27 +441,51 @@ bool LuckyEggFarmer::run_safari_zone(SingleSwitchProgramEnvironment& env, ProCon pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); } return true; - }else{ - env.update_stats(); + case -1: + default: + // Failed to find an encounter in the allotted time or ran out of steps in the grass. Soft Reset and try again. + return false; } - if (!is_chansey(env, context)){ - env.log("Not a Chansey. Fleeing..."); - flee_battle(env.console, context); - context.wait_for_all_requests(); - continue; + if (ITEM_TO_FARM == ItemToFarm::LUCKY_EGG){ + if (!is_chansey(env, context)){ + env.log("Not a Chansey. Fleeing..."); + flee_battle(env.console, context); + context.wait_for_all_requests(); + continue; + } + }else{ + if (!is_dragonair(env, context)){ + env.log("Not a Dragonair. Fleeing..."); + flee_battle(env.console, context); + context.wait_for_all_requests(); + continue; + } } - env.log("Chansey found!"); - stats.chanseys_found++; + env.log("Target Pokemon found!"); + stats.target_pokemon_found++; env.update_stats(); - bool caught = attempt_catch(env, context, balls_left); + std::string encounter_name = (ITEM_TO_FARM == ItemToFarm::LUCKY_EGG) ? "chansey" : "dragonair"; - if (caught){ - stats.chanseys_caught++; + int catch_result = auto_catch_safari(env.console, context, LANGUAGE, balls_left, encounter_name); + env.log("Catch result: " + std::to_string(catch_result)); + + bool caught = false; + if (catch_result < 0){ + stats.errors++; + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "auto_catch_safari() encountered an error.", + env.console + ); + } else if (catch_result == 1){ + env.log("Target Pokemon caught!"); + caught = true; + stats.target_pokemon_caught++; env.update_stats(); - chansey_count++; + party_count++; } pbf_wait(context, 500ms); @@ -491,12 +493,17 @@ bool LuckyEggFarmer::run_safari_zone(SingleSwitchProgramEnvironment& env, ProCon WhiteDialogDetector dialog(COLOR_RED); bool in_safari_zone_building = dialog.detect(env.console.video().snapshot()); + if (in_safari_zone_building){ + env.log("Detected Safari Zone building."); + } if (balls_left <= 0){ + env.log("Out of Safari balls."); in_safari_zone_building = true; } if (in_safari_zone_building && !caught){ + env.log("In Safari Zone building and target Pokemon not caught. Resetting."); return false; } @@ -506,25 +513,25 @@ bool LuckyEggFarmer::run_safari_zone(SingleSwitchProgramEnvironment& env, ProCon } if (caught){ - if (check_for_lucky_egg(env.console, context, in_safari_zone_building)){ - env.log("Lucky Egg found!"); - stats.eggs++; + if (check_for_held_item(env.console, context, in_safari_zone_building, party_count)){ + env.log("Held Item found!"); + stats.items++; env.update_stats(); if (TAKE_VIDEO){ pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); } send_program_notification( env, - NOTIFICATION_LUCKY_EGG, + NOTIFICATION_HELD_ITEM, Color(0xffffc0cb), - "Lucky Egg found!", + "Held Item found!", {}, "", env.console.video().snapshot(), true ); return true; } - env.log("Lucky Egg not found. Continuing to farm..."); + env.log("Held Item not found. Continuing to farm..."); pbf_mash_button(context, BUTTON_B, 1500ms); context.wait_for_all_requests(); } @@ -538,15 +545,41 @@ bool LuckyEggFarmer::run_safari_zone(SingleSwitchProgramEnvironment& env, ProCon return false; } -void LuckyEggFarmer::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ +void HeldItemFarmerSafariZone::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ home_black_border_check(env.console, context); DeferredStopButtonOption::ResetOnExit reset_on_exit(STOP_AFTER_CURRENT); + open_party_menu_from_overworld(env.console, context, StartMenuContext::STANDARD); + PartySlot detected_party_slot = detect_last_occupied_party_slot(env.console); + int party_count = (int)detected_party_slot + 1; + + if (party_count >= 6){ + throw UserSetupError(env.console, "Party is full. Please remove some Pokemon from the party and try again."); + } + + if (ITEM_TO_FARM == ItemToFarm::LUCKY_EGG && party_count == 1){ + throw UserSetupError(env.console, "Farming Chansey requires at least 2 Pokemon in the party."); + } + + StartMenuDetector start_menu_detector(COLOR_RED); + PartyMenuDetector party_menu_detector(COLOR_RED); + + while (true){ + pbf_mash_button(context, BUTTON_B, 1000ms); + context.wait_for_all_requests(); + VideoSnapshot snapshot = env.console.video().snapshot(); + if (!start_menu_detector.detect(snapshot) && !party_menu_detector.detect(snapshot)){ + break; + } + } + while (true){ + env.log("Walking forward to start Safari Zone dialog."); pbf_press_dpad(context, DPAD_UP, 200ms, 0ms); + WallClock start_time = current_time(); while (true){ BlackScreenWatcher safari_zone_exit(COLOR_RED); @@ -559,10 +592,18 @@ void LuckyEggFarmer::program(SingleSwitchProgramEnvironment& env, ProControllerC ); if (ret == 0){ + env.log("Detected Safari Zone transition."); + break; + } + + if (current_time() - start_time > std::chrono::seconds(60)){ + env.log("Didn't detect Safari Zone transition after 60 seconds. Assuming entered safari zone."); break; } } + start_time = current_time(); + bool detected_overworld = false; while (true){ BlackScreenOverWatcher overworld_entered(COLOR_RED); @@ -573,25 +614,47 @@ void LuckyEggFarmer::program(SingleSwitchProgramEnvironment& env, ProControllerC ); if (ret == 0){ + env.log("Detected overworld entered."); + detected_overworld = true; break; } + + if (current_time() - start_time > std::chrono::seconds(20)){ + env.log("Didn't detect overworld entered after 20 seconds. Checking if in overworld."); + if (!in_overworld(env.console, context)){ + env.log("Couldn't detect overworld. Resetting."); + soft_reset(env.console, context); + break; + } + detected_overworld = true; + break; + } + } + + if (!detected_overworld){ + continue; } // There is a small delay from seeing the overworld to being able to actually move. pbf_wait(context, 2000ms); context.wait_for_all_requests(); - if (!navigate_to_chansey(env.console, context)){ - env.console.log("Navigation failed. Resetting..."); - soft_reset(env.console, context); - continue; - } + if (ITEM_TO_FARM == ItemToFarm::LUCKY_EGG){ + if (!navigate_to_chansey(env.console, context)){ + env.console.log("Navigation failed. Resetting..."); + soft_reset(env.console, context); + continue; + } + + swap_lead_pokemon(env.console, context); - swap_lead_pokemon(env.console, context); + }else{ // Dragon Fang + navigate_to_dragonair(env.console, context); + } - if (run_safari_zone(env, context)){ + if (run_safari_zone(env, context, party_count)){ GO_HOME_WHEN_DONE.run_end_of_program(context); - return; // Already sent notification in run_safari_zone if shiny or lucky egg found. + return; // Already sent notification in run_safari_zone if shiny or held item found. } soft_reset(env.console, context); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_HeldItemFarmer-SafariZone.h similarity index 57% rename from SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h rename to SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_HeldItemFarmer-SafariZone.h index 995d7b06b1..77cc619417 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_HeldItemFarmer-SafariZone.h @@ -1,11 +1,11 @@ -/* Lucky Egg Farmer +/* Held Item Farmer - Safari Zone * * From: https://github.com/PokemonAutomation/ * */ -#ifndef PokemonAutomation_PokemonFRLG_LuckyEggFarmer_H -#define PokemonAutomation_PokemonFRLG_LuckyEggFarmer_H +#ifndef PokemonAutomation_PokemonFRLG_HeldItemFarmer_SafariZone_H +#define PokemonAutomation_PokemonFRLG_HeldItemFarmer_SafariZone_H #include "Common/Cpp/Options/ButtonOption.h" #include "CommonTools/Options/LanguageOCROption.h" @@ -19,16 +19,16 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -class LuckyEggFarmer_Descriptor : public SingleSwitchProgramDescriptor{ +class HeldItemFarmerSafariZone_Descriptor : public SingleSwitchProgramDescriptor{ public: - LuckyEggFarmer_Descriptor(); + HeldItemFarmerSafariZone_Descriptor(); struct Stats; virtual std::unique_ptr make_stats() const override; }; -class LuckyEggFarmer : public SingleSwitchProgramInstance { +class HeldItemFarmerSafariZone : public SingleSwitchProgramInstance { public: - LuckyEggFarmer(); + HeldItemFarmerSafariZone(); virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext& context) override; virtual void start_program_border_check( VideoStream& stream, @@ -37,29 +37,38 @@ class LuckyEggFarmer : public SingleSwitchProgramInstance { } private: + enum class ItemToFarm{ + LUCKY_EGG, + DRAGON_FANG + }; + + static EnumDropdownDatabase item_to_farm_options(); // After exiting the safari zone building navigate to grass with Chansey. // Currently only supports running. Should add Surf option... bool navigate_to_chansey(ConsoleHandle& console, ProControllerContext& context); + // After exiting the safari zone building navigate to water with Dragonair. + // Currently only supports running. + void navigate_to_dragonair(ConsoleHandle& console, ProControllerContext& context); // Swap first and second pokemon. // First pokemon used to avoid encounters on the route to Chansey. Second pokemon used to improve encounter rates. void swap_lead_pokemon(ConsoleHandle& console, ProControllerContext& context); - // Reads wild encounter name and returns true if Chansey + // Reads wild encounter name and returns true if Chansey, uses a set list of possible names in the expected area. bool is_chansey(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - // Handles the encounter logic. Attempts to spin in place. Resets position to the top right corner of grass. - // Returns true if transition to battle detected. - bool find_encounter(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - // Handles the catch logic. Should be updated to throw bait for better catch rates. - // Returns true if catch successful. Returns in the overworld. - bool attempt_catch(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int& balls_left); + // Reads wild encounter name and returns true if Dragonair, uses a set list of possible names in the expected area. + bool is_dragonair(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + // Handles the grass encounter logic. Attempts to spin in place. Resets position to the top right corner of grass. + // returns -1 if no encounter is triggered, 0 if a non-shiny is encountered, and 1 if a shiny is encountered + int find_encounter_grass(SingleSwitchProgramEnvironment& env, ProControllerContext& context); // Opens the party menu from a given overworld location (safari zone, or the main safari zone building) // Checks the last four party slots for held items. Returns true if item detected. - bool check_for_lucky_egg(ConsoleHandle& console, ProControllerContext& context, bool returned_to_building); + bool check_for_held_item(ConsoleHandle& console, ProControllerContext& context, bool returned_to_building, int party_count); // Handles the main loop once we are in the grass ready to search for a Chansey. // Returns true if a stop condition is met (lucky egg or shiny found). // Returns false if we need to soft reset (out of safari balls, out of steps, caught a full party of Chansey). - bool run_safari_zone(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + bool run_safari_zone(SingleSwitchProgramEnvironment& env, ProControllerContext& context, int party_count); + EnumDropdownOption ITEM_TO_FARM; OCR::LanguageOCROption LANGUAGE; DeferredStopButtonOption STOP_AFTER_CURRENT; @@ -67,7 +76,7 @@ class LuckyEggFarmer : public SingleSwitchProgramInstance { GoHomeWhenDoneOption GO_HOME_WHEN_DONE; EventNotificationOption NOTIFICATION_STATUS_UPDATE; - EventNotificationOption NOTIFICATION_LUCKY_EGG; + EventNotificationOption NOTIFICATION_HELD_ITEM; EventNotificationOption NOTIFICATION_SHINY; EventNotificationsOption NOTIFICATIONS; diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index b05a5537ab..793e2944bc 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1629,10 +1629,10 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/PokemonFRLG_Settings.h Source/PokemonFRLG/PokemonFRLG_Tests.cpp Source/PokemonFRLG/PokemonFRLG_Tests.h + Source/PokemonFRLG/Programs/Farming/PokemonFRLG_HeldItemFarmer-SafariZone.cpp + Source/PokemonFRLG/Programs/Farming/PokemonFRLG_HeldItemFarmer-SafariZone.h Source/PokemonFRLG/Programs/Farming/PokemonFRLG_ItemDuplication.cpp Source/PokemonFRLG/Programs/Farming/PokemonFRLG_ItemDuplication.h - Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.cpp - Source/PokemonFRLG/Programs/Farming/PokemonFRLG_LuckyEggFarmer.h Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.cpp Source/PokemonFRLG/Programs/Farming/PokemonFRLG_NuggetBridgeFarmer.h Source/PokemonFRLG/Programs/Farming/PokemonFRLG_PickupFarmer.cpp