diff --git a/SerialPrograms/CMakeLists.txt b/SerialPrograms/CMakeLists.txt index 3f0bef0a4a..ee1f975e9c 100644 --- a/SerialPrograms/CMakeLists.txt +++ b/SerialPrograms/CMakeLists.txt @@ -1360,6 +1360,12 @@ file(GLOB MAIN_SOURCES Source/PokemonLA/Resources/PokemonLA_PokemonSprites.h Source/PokemonLA/Resources/PokemonLA_WeatherAndTimeIcons.cpp Source/PokemonLA/Resources/PokemonLA_WeatherAndTimeIcons.h + Source/PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.cpp + Source/PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.h + Source/PokemonLGPE/Programs/PokemonLGPE_AlolanTrade.cpp + Source/PokemonLGPE/Programs/PokemonLGPE_AlolanTrade.h + Source/PokemonLGPE/PokemonLGPE_Panels.cpp + Source/PokemonLGPE/PokemonLGPE_Panels.h Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h Source/PokemonRSE/Inference/Sounds/PokemonRSE_ShinySoundDetector.cpp diff --git a/SerialPrograms/SerialPrograms.pro b/SerialPrograms/SerialPrograms.pro index f66a059d25..5ab09656f8 100644 --- a/SerialPrograms/SerialPrograms.pro +++ b/SerialPrograms/SerialPrograms.pro @@ -667,6 +667,9 @@ SOURCES += \ Source/PokemonLA/Resources/PokemonLA_NameDatabase.cpp \ Source/PokemonLA/Resources/PokemonLA_PokemonSprites.cpp \ Source/PokemonLA/Resources/PokemonLA_WeatherAndTimeIcons.cpp \ + Source/PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.cpp \ + Source/PokemonLGPE/Programs/PokemonLGPE_AlolanTrade.cpp \ + Source/PokemonLGPE/PokemonLGPE_Panels.cpp \ Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.cpp \ Source/PokemonRSE/Inference/PokemonRSE_ShinyNumberDetector.cpp \ Source/PokemonRSE/Inference/Sounds/PokemonRSE_ShinySoundDetector.cpp \ @@ -1846,6 +1849,9 @@ HEADERS += \ Source/PokemonLA/Resources/PokemonLA_NameDatabase.h \ Source/PokemonLA/Resources/PokemonLA_PokemonSprites.h \ Source/PokemonLA/Resources/PokemonLA_WeatherAndTimeIcons.h \ + Source/PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.h \ + Source/PokemonLGPE/Programs/PokemonLGPE_AlolanTrade.h \ + Source/PokemonLGPE/PokemonLGPE_Panels.h \ Source/PokemonRSE/Inference/Dialogs/PokemonRSE_DialogDetector.h \ Source/PokemonRSE/Inference/PokemonRSE_ShinyNumberDetector.h \ Source/PokemonRSE/Inference/Sounds/PokemonRSE_ShinySoundDetector.h \ diff --git a/SerialPrograms/Source/PanelLists.cpp b/SerialPrograms/Source/PanelLists.cpp index de6fafbf13..1a4eae1de5 100644 --- a/SerialPrograms/Source/PanelLists.cpp +++ b/SerialPrograms/Source/PanelLists.cpp @@ -18,6 +18,7 @@ #include "PokemonHome/PokemonHome_Panels.h" #include "PokemonBDSP/PokemonBDSP_Panels.h" #include "PokemonLA/PokemonLA_Panels.h" +#include "PokemonLGPE/PokemonLGPE_Panels.h" #include "PokemonRSE/PokemonRSE_Panels.h" #include "PokemonSV/PokemonSV_Panels.h" #include "ZeldaTotK/ZeldaTotK_Panels.h" @@ -51,6 +52,7 @@ ProgramSelect::ProgramSelect(QWidget& parent, PanelHolder& holder) add(std::make_unique()); if (PreloadSettings::instance().DEVELOPER_MODE) { add(std::make_unique()); + add(std::make_unique()); } add(std::make_unique()); diff --git a/SerialPrograms/Source/PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.cpp b/SerialPrograms/Source/PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.cpp new file mode 100644 index 0000000000..2f4d3f67e1 --- /dev/null +++ b/SerialPrograms/Source/PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.cpp @@ -0,0 +1,49 @@ +/* Shiny Symbol Detector + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#include "Common/Cpp/Color.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Images/ImageFilter.h" +#include "PokemonLGPE_ShinySymbolDetector.h" + +#include +using std::cout; +using std::endl; + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonLGPE{ + +ShinySymbolDetector::ShinySymbolDetector(Color color) + : m_box_star(0.666, 0.779, 0.028, 0.044) +{} +void ShinySymbolDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(COLOR_RED, m_box_star); +} +bool ShinySymbolDetector::read(Logger& logger, const ImageViewRGB32& frame){ + /* + Shiny (charizard): + Add infer box: (0.6660, 0.7790, 0.0280, 0.0440), RGB avg [159, 123, 125] avg sum 408 ratio [0.391, 0.301, 0.308] stddev [74.898, 54.696, 53.354] sum 182.948 crop size (54, 48) + + Not shiny (chansey): + Add infer box: (0.6660, 0.7790, 0.0280, 0.0440), RGB avg [82, 113, 100] avg sum 295 ratio [0.276, 0.384, 0.340] stddev [15.477, 2.178, 2.648] sum 20.303 crop size (54, 48) + + Only had the two to test with for now. + */ + + const auto stats = image_stats(extract_box_reference(frame, m_box_star)); + return stats.stddev.sum() > 100; +} + + +} +} +} + diff --git a/SerialPrograms/Source/PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.h b/SerialPrograms/Source/PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.h new file mode 100644 index 0000000000..742f69a365 --- /dev/null +++ b/SerialPrograms/Source/PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.h @@ -0,0 +1,35 @@ +/* Shiny Symbol Detector + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#ifndef PokemonAutomation_PokemonRSE_ShinyNumberDetector_H +#define PokemonAutomation_PokemonRSE_ShinyNumberDetector_H + +#include "Common/Cpp/AbstractLogger.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonLGPE{ + +//Check for the red shiny star on a Pokemon's summary from the Party/Box menu. +//This does not work for the summary that appears after a catch. +class ShinySymbolDetector{ +public: + ShinySymbolDetector(Color color); + + virtual void make_overlays(VideoOverlaySet& items) const; + bool read(Logger& logger, const ImageViewRGB32& frame); + +private: + ImageFloatBox m_box_star; +}; + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonLGPE/PokemonLGPE_Panels.cpp b/SerialPrograms/Source/PokemonLGPE/PokemonLGPE_Panels.cpp new file mode 100644 index 0000000000..75bf953aba --- /dev/null +++ b/SerialPrograms/Source/PokemonLGPE/PokemonLGPE_Panels.cpp @@ -0,0 +1,40 @@ +/* Pokemon LGPE Panels + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#include "CommonFramework/GlobalSettingsPanel.h" +#include "Pokemon/Pokemon_Strings.h" +#include "PokemonLGPE_Panels.h" + +#include "Programs/PokemonLGPE_AlolanTrade.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonLGPE{ + +PanelListFactory::PanelListFactory() + : PanelListDescriptor(Pokemon::STRING_POKEMON + " Let's Go Pikachu and Eevee") +{} + +std::vector PanelListFactory::make_panels() const{ + std::vector ret; + + ret.emplace_back("---- Settings ----"); + //ret.emplace_back(make_settings()); + + //ret.emplace_back("---- General ----"); + + ret.emplace_back("---- Shiny Hunting ----"); + ret.emplace_back(make_single_switch_program()); + + return ret; +} + + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonLGPE/PokemonLGPE_Panels.h b/SerialPrograms/Source/PokemonLGPE/PokemonLGPE_Panels.h new file mode 100644 index 0000000000..20bed0a1d2 --- /dev/null +++ b/SerialPrograms/Source/PokemonLGPE/PokemonLGPE_Panels.h @@ -0,0 +1,27 @@ +/* Pokemon LGPE Panels + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#ifndef PokemonAutomation_PokemonLGPE_Panels_H +#define PokemonAutomation_PokemonLGPE_Panels_H + +#include "CommonFramework/Panels/PanelList.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonLGPE{ + +class PanelListFactory : public PanelListDescriptor{ +public: + PanelListFactory(); + virtual std::vector make_panels() const; +}; + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonLGPE/Programs/PokemonLGPE_AlolanTrade.cpp b/SerialPrograms/Source/PokemonLGPE/Programs/PokemonLGPE_AlolanTrade.cpp new file mode 100644 index 0000000000..d014ea7f17 --- /dev/null +++ b/SerialPrograms/Source/PokemonLGPE/Programs/PokemonLGPE_AlolanTrade.cpp @@ -0,0 +1,278 @@ +/* LGPE Alolan Trade + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/StartupChecks/VideoResolutionCheck.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/Controllers/NintendoSwitch_Joycon.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" +#include "Pokemon/Pokemon_Strings.h" +#include "CommonTools/VisualDetectors/BlackScreenDetector.h" +#include "PokemonLGPE/Inference/PokemonLGPE_ShinySymbolDetector.h" +#include "PokemonLGPE_AlolanTrade.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonLGPE{ + +AlolanTrade_Descriptor::AlolanTrade_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonLGPE:AlolanTrade", + Pokemon::STRING_POKEMON + " LGPE", "Alolan Trade", + "", + "Shiny hunt Alolan forms by trading in-game.", + FeedbackType::NONE, + AllowCommandsWhenRunning::DISABLE_COMMANDS, + {ControllerFeature::NintendoSwitch_RightJoycon}, + FasterIfTickPrecise::NOT_FASTER + ) +{} + +struct AlolanTrade_Descriptor::Stats : public StatsTracker{ + Stats() + : trades(m_stats["Trades"]) + , resets(m_stats["Resets"]) + , shinies(m_stats["Shinies"]) + { + m_display_order.emplace_back("Trades"); + m_display_order.emplace_back("Resets"); + m_display_order.emplace_back("Shinies"); + } + std::atomic& trades; + std::atomic& resets; + std::atomic& shinies; +}; +std::unique_ptr AlolanTrade_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +AlolanTrade::AlolanTrade() + : NUM_TRADES( + "Number of Pokemon to trade:", + LockMode::LOCK_WHILE_RUNNING, + 30, 1 + ) + , GO_HOME_WHEN_DONE(false) + , NOTIFICATION_SHINY( + "Shiny Found", + true, true, ImageAttachmentMode::JPG, + {"Notifs", "Showcase"} + ) + , NOTIFICATION_STATUS_UPDATE("Status Update", true, false, std::chrono::seconds(3600)) + , NOTIFICATIONS({ + &NOTIFICATION_SHINY, + &NOTIFICATION_STATUS_UPDATE, + &NOTIFICATION_PROGRAM_FINISH, + }) +{ + PA_ADD_OPTION(NUM_TRADES); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); +} + +void AlolanTrade::run_trade(SingleSwitchProgramEnvironment& env, JoyconContext& context){ + //Talk to NPC, say Yes, select Pokemon from box. + BlackScreenOverWatcher trade_started(COLOR_RED); + int ret = run_until( + env.console, context, + [](JoyconContext& context){ + pbf_mash_button(context, BUTTON_A, 15000ms); + }, + {trade_started} + ); + context.wait_for_all_requests(); + if (ret != 0){ + env.log("Failed to start trade.", COLOR_RED); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Failed to start trade.", + env.console + ); + } + else { + env.log("Trade started."); + } + + //Wait for trade to complete. + BlackScreenOverWatcher trade_completed(COLOR_YELLOW); + int ret2 = wait_until( + env.console, context, + std::chrono::seconds(120), + {trade_completed} + ); + context.wait_for_all_requests(); + if (ret2 != 0){ + env.log("Did not detect end of trade.", COLOR_RED); + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "Did not detect end of trade.", + env.console + ); + } + else { + env.log("Trade completed."); + } + + //Summary will appear the first time you trade in a session(?) + //Sometimes it appears anyway, don't know what determines it + //Exit menu and dialog. + pbf_mash_button(context, BUTTON_B, 3000ms); + context.wait_for_all_requests(); +} + +void AlolanTrade::program(SingleSwitchProgramEnvironment& env, CancellableScope& scope){ + JoyconContext context(scope, env.console.controller()); + assert_16_9_720p_min(env.logger(), env.console); + AlolanTrade_Descriptor::Stats& stats = env.current_stats(); + + /* + WARNING: JOYCON TEST PROGRAM. Not well tested. Bare minimum in general. + + Only works with Right joycon atm. Do not update right joycon. + + Right joycon required for home button (this means no on-switch screenshots). + Also don't remap any of the buttons in the switch button mapping settings. Yet? Could use this to add Home and Screenshot. + + Preconditions: + DO NOT have any Pokemon you want to keep in your boxes. Move them out to Home first. + Favoriting a Pokemon does not prevent it from being traded. + This must not be your first time doing the trade. (I've done all the trades, so I can't check first time trade behavior.) + + Setup: + Catch the Kanto variant of the target. Put this number in NUM_TRADES. + Stand in front of trade NPC. + Start the program in-game. + + Future additions?: + skip favorited pokemon - they have a symbol to indicate fav status (imo still risky to do, just move them out) + detect when all pokemon traded - trading screen will open but text will appear in the center + "you don't have any pokemon your trading partner wants" + detect dialog box, detect yes/no confirm box + actual enter game and start screen detectors + menu detectors, reset game from home, etc. + get rid of all this blindly mashing A in general...so need everything really. + */ + + bool shiny_found = false; + while (!shiny_found) { + //Run trades + for (uint16_t i = 0; i < NUM_TRADES; i++) { + run_trade(env, context); + + stats.trades++; + env.update_stats(); + } + + env.log("Done trading. Checking boxes."); + send_program_status_notification( + env, NOTIFICATION_STATUS_UPDATE, + "Done trading. Checking boxes." + ); + + //To check pokemon in menu boxes + //Open menu - always defaults to center (Party) + /* Menu: + Play with Partner + Pokedex - Bag - Party - Communicate - Save (these all have a colored line under when selected) + (Press Y for options) + */ + + //Wait a bit. + pbf_wait(context, 2500ms); + context.wait_for_all_requests(); + + //Open menu, open party, open boxes + env.log("Opening boxes."); + pbf_press_button(context, BUTTON_X, 200ms, 500ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + pbf_press_button(context, BUTTON_Y, 200ms, 1500ms); + context.wait_for_all_requests(); + + //Sort by order caught + env.log("Sorting by order caught."); + pbf_press_button(context, BUTTON_Y, 200ms, 1000ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + context.wait_for_all_requests(); + + //Press left to go to last (most recent) Pokemon + env.log("Opening summary of most recent Pokemon."); + pbf_move_joystick(context, 0, 128, 100ms, 100ms); + context.wait_for_all_requests(); + + //View summary - it takes a moment to load + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + pbf_move_joystick(context, 128, 255, 100ms, 100ms); + pbf_move_joystick(context, 128, 255, 100ms, 100ms); + pbf_press_button(context, BUTTON_A, 200ms, 100ms); + context.wait_for_all_requests(); + + pbf_wait(context, 5000ms); + context.wait_for_all_requests(); + + //Now check for shinies. Check everything that was traded. + for (uint16_t i = 0; i < NUM_TRADES; i++) { + VideoSnapshot screen = env.console.video().snapshot(); + ShinySymbolDetector shiny_checker(COLOR_YELLOW); + bool check = shiny_checker.read(env.console.logger(), screen); + + if (check) { + env.log("Shiny detected!"); + stats.shinies++; + env.update_stats(); + send_program_notification(env, NOTIFICATION_SHINY, COLOR_YELLOW, "Shiny found!", {}, "", screen, true); + shiny_found = true; + } + else { + env.log("Not shiny."); + } + + //Move left, check next. + pbf_move_joystick(context, 0, 128, 100ms, 100ms); + pbf_press_button(context, BUTTON_X, 0ms, 2000ms); + context.wait_for_all_requests(); + } + /* + if (!shiny_found) { + env.log("Out of Pokemon to trade and no shiny found. Resetting game."); + send_program_status_notification( + env, NOTIFICATION_STATUS_UPDATE, + "Out of Pokemon to trade and no shiny found. Resetting game." + ); + + //TODO: Need to make proper GameEntry eventually + //Thankfully, Joycon is upright after going to home. + //Go to home and close game + pbf_press_button(context, BUTTON_HOME, 200ms, 3000ms); + pbf_press_button(context, BUTTON_X, 200ms, 200ms); + pbf_press_button(context, BUTTON_A, 200ms, 1000ms); + + //TODO: + //joycon context->pro controller context? + start_game_from_home(env.console, context, true, 0, 0, std::chrono::milliseconds(2000)); + + stats.resets++; + env.update_stats(); + } + */ + //Break for now since resetting the game doesn't work. + break; + } + + if (GO_HOME_WHEN_DONE) { + pbf_press_button(context, BUTTON_HOME, 200ms, 1000ms); + } + send_program_finished_notification(env, NOTIFICATION_PROGRAM_FINISH); +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonLGPE/Programs/PokemonLGPE_AlolanTrade.h b/SerialPrograms/Source/PokemonLGPE/Programs/PokemonLGPE_AlolanTrade.h new file mode 100644 index 0000000000..3fa44a0f06 --- /dev/null +++ b/SerialPrograms/Source/PokemonLGPE/Programs/PokemonLGPE_AlolanTrade.h @@ -0,0 +1,53 @@ +/* LGPE Alolan Trade + * + * From: https://github.com/PokemonAutomation/Arduino-Source + * + */ + +#ifndef PokemonAutomation_PokemonLGPE_AlolanTrade_H +#define PokemonAutomation_PokemonLGPE_AlolanTrade_H + +#include "NintendoSwitch/Controllers/NintendoSwitch_Joycon.h" +#include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" +#include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" +#include "CommonFramework/Notifications/EventNotificationsTable.h" +#include "Common/Cpp/Options/SimpleIntegerOption.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonLGPE{ + +class AlolanTrade_Descriptor : public SingleSwitchProgramDescriptor{ +public: + AlolanTrade_Descriptor(); + struct Stats; + virtual std::unique_ptr make_stats() const override; +}; + +class AlolanTrade : public SingleSwitchProgramInstance{ +public: + AlolanTrade(); + virtual void program(SingleSwitchProgramEnvironment& env, CancellableScope& scope) override; + +private: + void run_trade(SingleSwitchProgramEnvironment& env, JoyconContext& context); + + SimpleIntegerOption NUM_TRADES; + + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + + EventNotificationOption NOTIFICATION_SHINY; + EventNotificationOption NOTIFICATION_STATUS_UPDATE; + EventNotificationsOption NOTIFICATIONS; +}; + + + + +} +} +} +#endif + + +