From d661806a56d535e5f614178a417779d649054b69 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 26 Apr 2026 03:50:33 -0500 Subject: [PATCH 01/12] gift rng program --- .../Dialogs/PokemonFRLG_PartyDialogs.cpp | 122 ++++ .../Dialogs/PokemonFRLG_PartyDialogs.h | 99 +++ .../Menus/PokemonFRLG_BagDetector.cpp | 61 ++ .../Inference/Menus/PokemonFRLG_BagDetector.h | 50 ++ .../Menus/PokemonFRLG_PartyMenuDetector.cpp | 25 - .../Menus/PokemonFRLG_PartyMenuDetector.h | 20 - .../PokemonFRLG_PartyLevelUpReader.cpp | 94 +++ .../PokemonFRLG_PartyLevelUpReader.h | 53 ++ .../PokemonFRLG/PokemonFRLG_Navigation.cpp | 61 ++ .../PokemonFRLG/PokemonFRLG_Navigation.h | 3 + .../Source/PokemonFRLG/PokemonFRLG_Panels.cpp | 2 + .../Farming/PokemonFRLG_EvTrainer.cpp | 1 + .../Farming/PokemonFRLG_PickupFarmer.cpp | 1 + .../PokemonFRLG_BlindNavigation.cpp | 14 + .../PokemonFRLG_BlindNavigation.h | 7 + .../RngManipulation/PokemonFRLG_GiftRng.cpp | 688 ++++++++++++++++++ .../RngManipulation/PokemonFRLG_GiftRng.h | 96 +++ .../PokemonFRLG_RngCalibration.cpp | 327 +++++++++ .../PokemonFRLG_RngCalibration.h | 91 +++ .../PokemonFRLG_RngDisplays.cpp | 40 +- .../RngManipulation/PokemonFRLG_RngDisplays.h | 23 +- .../PokemonFRLG_StarterRng.cpp | 355 +-------- .../RngManipulation/PokemonFRLG_StarterRng.h | 57 +- SerialPrograms/cmake/SourceFiles.cmake | 10 + 24 files changed, 1856 insertions(+), 444 deletions(-) create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp create mode 100644 SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.cpp new file mode 100644 index 0000000000..4cdfb3b203 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.cpp @@ -0,0 +1,122 @@ +/* Party Dialog Detectors + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "CommonTools/Images/SolidColorTest.h" +#include "CommonTools/Images/ImageFilter.h" +#include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG_PartyDialogs.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +PartySelectionDetector::PartySelectionDetector(Color color) + : m_dialog_right_box(0.955, 0.648, 0.010, 0.303) + , m_page_background_box(0.028, 0.500, 0.010, 0.250) +{} +void PartySelectionDetector::make_overlays(VideoOverlaySet& items) const{ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_page_background_box)); +} +bool PartySelectionDetector::detect(const ImageViewRGB32& screen){ + ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); + + //Dialog is white + ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box); + + //Menu background is teal + ImageViewRGB32 menu_background_image = extract_box_reference(game_screen, m_page_background_box); + + if (is_white(dialog_right_image) + && is_solid(menu_background_image, { 0.020, 0.424, 0.412 }, 0.25, 20) //5, 108, 105 teal + ){ + return true; + } + return false; +} + + +PartyLevelUpDetector::PartyLevelUpDetector(Color color, PartyLevelUpDialog dialog_type) + : dialog_type(dialog_type) + , m_border_top_box(0.619231, 0.023038, 0.362179, 0.001923) // gray (120, 115, 140) + , m_border_right_box(0.982692, 0.025923, 0.001923, 0.597115) + , m_dialog_top_box(0.626282, 0.042492, 0.341026, 0.006175) // white + , m_dialog_right_box(0.967949, 0.050923, 0.003846, 0.550962) + , m_plus_box(0.862663, 0.051852, 0.034267, 0.553560) +{} +void PartyLevelUpDetector::make_overlays(VideoOverlaySet& items) const{ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_border_top_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_border_right_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box)); +} +bool PartyLevelUpDetector::detect(const ImageViewRGB32& screen){ + ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); + + ImageViewRGB32 border_top_image = extract_box_reference(game_screen, m_border_top_box); + ImageViewRGB32 border_right_image = extract_box_reference(game_screen, m_border_right_box); + + ImageViewRGB32 dialog_top_image = extract_box_reference(game_screen, m_dialog_top_box); + ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box); + + //Plus box is not solid white on the first screen, but is white on the second one + ImageViewRGB32 plus_image = extract_box_reference(game_screen, m_plus_box); + bool good_plus_image = ( + dialog_type == PartyLevelUpDialog::either + || (dialog_type == PartyLevelUpDialog::plus && !is_white(plus_image)) + || (dialog_type == PartyLevelUpDialog::stats && is_white(plus_image)) + ); + + if (is_solid(border_top_image, { 0.320, 0.307, 0.373 }, 0.25, 20) + && is_solid(border_right_image, { 0.320, 0.307, 0.373 }, 0.25, 20) + && is_white(dialog_top_image) + && is_white(dialog_right_image) + && good_plus_image + ){ + return true; + } + return false; +} + +PartyMoveLearnDetector::PartyMoveLearnDetector(Color color) + : m_border_top_box(0.685096, 0.422836, 0.229327, 0.002163) // gray (120, 115, 140) + , m_border_right_box(0.916346, 0.426442, 0.001442, 0.245913) + , m_dialog_top_box(0.699038, 0.445913, 0.200000, 0.006490) // white + , m_dialog_right_box(0.899519, 0.454567, 0.004327, 0.194712) +{} +void PartyMoveLearnDetector::make_overlays(VideoOverlaySet& items) const{ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_border_top_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_border_right_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_top_box)); + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box)); +} +bool PartyMoveLearnDetector::detect(const ImageViewRGB32& screen){ + ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); + + ImageViewRGB32 border_top_image = extract_box_reference(game_screen, m_border_top_box); + ImageViewRGB32 border_right_image = extract_box_reference(game_screen, m_border_right_box); + + ImageViewRGB32 dialog_top_image = extract_box_reference(game_screen, m_dialog_top_box); + ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box); + + if (is_solid(border_top_image, { 0.320, 0.307, 0.373 }, 0.25, 20) + && is_solid(border_right_image, { 0.320, 0.307, 0.373 }, 0.25, 20) + && is_white(dialog_top_image) + && is_white(dialog_right_image) + ){ + return true; + } + return false; +} + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h new file mode 100644 index 0000000000..4d6e711112 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h @@ -0,0 +1,99 @@ +/* Party Dialog Detectors + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_PartyDialogs_H +#define PokemonAutomation_PokemonFRLG_PartyDialogs_H + +#include +#include "Common/Cpp/Color.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" +#include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" + +namespace PokemonAutomation{ + class CancellableScope; + class VideoFeed; +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +// The Party menu has a white box in the bottom right corner when a Pokemon is selected +// The background around the edges is dark teal/navy +class PartySelectionDetector : public StaticScreenDetector{ +public: + PartySelectionDetector(Color color); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + ImageFloatBox m_dialog_right_box; + ImageFloatBox m_page_background_box; +}; +class PartySelectionWatcher : public DetectorToFinder{ +public: + PartySelectionWatcher(Color color) + : DetectorToFinder("PartySelectionWatcher", std::chrono::milliseconds(250), color) + {} +}; + +enum class PartyLevelUpDialog{ + plus, + stats, + either +}; + +class PartyLevelUpDetector : public StaticScreenDetector{ +public: + PartyLevelUpDetector(Color color, PartyLevelUpDialog dialog_type); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + PartyLevelUpDialog dialog_type; + ImageFloatBox m_border_top_box; + ImageFloatBox m_border_right_box; + ImageFloatBox m_dialog_top_box; + ImageFloatBox m_dialog_right_box; + ImageFloatBox m_plus_box; +}; +class PartyLevelUpWatcher : public DetectorToFinder{ +public: + PartyLevelUpWatcher(Color color, PartyLevelUpDialog dialog_type) + : DetectorToFinder("BattleLevelUpWatcher", std::chrono::milliseconds(250), color, dialog_type) + {} +}; + + +class PartyMoveLearnDetector : public StaticScreenDetector{ +public: + PartyMoveLearnDetector(Color color); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + ImageFloatBox m_border_top_box; + ImageFloatBox m_border_right_box; + ImageFloatBox m_dialog_top_box; + ImageFloatBox m_dialog_right_box; +}; +class PartyMoveLearnWatcher : public DetectorToFinder{ +public: + PartyMoveLearnWatcher(Color color) + : DetectorToFinder("PartyMoveLearnWatcher", std::chrono::milliseconds(250), color) + {} +}; + + + +} +} +} + +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.cpp new file mode 100644 index 0000000000..750e72d2f3 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.cpp @@ -0,0 +1,61 @@ +/* Bag Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Exceptions.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/ImageTypes/ImageRGB32.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Images/SolidColorTest.h" +#include "CommonTools/Images/ImageFilter.h" +#include "CommonTools/Images/WaterfillUtilities.h" +#include "PokemonFRLG_BagDetector.h" + + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +BagDetector::BagDetector(Color color) + : m_bag_shadow_box(0.130769, 0.594471, 0.070192, 0.015865) + , m_pocket_underline_box(0.043269, 0.137259, 0.279808, 0.005769) + , m_menu_top_box(0.029808, 0.029086, 0.947115, 0.003606) + , m_menu_right_box(0.977404, 0.034134, 0.005288, 0.597837) +{} +void BagDetector::make_overlays(VideoOverlaySet& items) const{ + const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_bag_shadow_box)); // Gray RGB(159, 159, 159) + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_pocket_underline_box)); // Orange RGB(255, 152, 50) + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_menu_top_box)); // Yellow RGB(255, 225, 93) + items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_menu_right_box)); // Yellow + +} +bool BagDetector::detect(const ImageViewRGB32& screen){ + ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); + + ImageViewRGB32 bag_shadow_image = extract_box_reference(game_screen, m_bag_shadow_box); + ImageViewRGB32 pocket_underline_image = extract_box_reference(game_screen, m_pocket_underline_box); + ImageViewRGB32 menu_top_image = extract_box_reference(game_screen, m_menu_top_box); + ImageViewRGB32 menu_right_image = extract_box_reference(game_screen, m_menu_right_box); + + + if ( is_solid(bag_shadow_image, { 0.333, 0.333, 0.333 }, 0.25, 20) + && is_solid(pocket_underline_image, { 0.558, 0.333, 0.109 }, 0.25, 20) + && is_solid(menu_top_image, { 0.445, 0.393, 0.162 }, 0.25, 20) + && is_solid(menu_right_image, { 0.445, 0.393, 0.162 }, 0.25, 20) + ){ + return true; + } + return false; +} + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h new file mode 100644 index 0000000000..03b855da49 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h @@ -0,0 +1,50 @@ +/* Bag Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_BagDetector_H +#define PokemonAutomation_PokemonFRLG_BagDetector_H + +#include +#include "Common/Cpp/Color.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" +#include "CommonTools/InferenceCallbacks/VisualInferenceCallback.h" +#include "PokemonFRLG/PokemonFRLG_Settings.h" + +namespace PokemonAutomation{ + class CancellableScope; + class VideoFeed; +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +class BagDetector : public StaticScreenDetector{ +public: + BagDetector(Color color); + + virtual void make_overlays(VideoOverlaySet& items) const override; + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + ImageFloatBox m_bag_shadow_box; + ImageFloatBox m_pocket_underline_box; + ImageFloatBox m_menu_top_box; + ImageFloatBox m_menu_right_box; +}; +class BagWatcher : public DetectorToFinder{ +public: + BagWatcher(Color color) + : DetectorToFinder("BagWatcher", std::chrono::milliseconds(250), color) + {} +}; + + + +} +} +} + +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.cpp index 7ea3981ccf..593cc85a81 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.cpp @@ -47,31 +47,6 @@ bool PartyMenuDetector::detect(const ImageViewRGB32& screen){ return false; } -PartySelectionDetector::PartySelectionDetector(Color color) - : m_dialog_right_box(0.955, 0.648, 0.010, 0.303) - , m_page_background_box(0.028, 0.500, 0.010, 0.250) -{} -void PartySelectionDetector::make_overlays(VideoOverlaySet& items) const{ - const BoxOption& GAME_BOX = GameSettings::instance().GAME_BOX; - items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_dialog_right_box)); - items.add(COLOR_RED, GAME_BOX.inner_to_outer(m_page_background_box)); -} -bool PartySelectionDetector::detect(const ImageViewRGB32& screen){ - ImageViewRGB32 game_screen = extract_box_reference(screen, GameSettings::instance().GAME_BOX); - - //Dialog is white - ImageViewRGB32 dialog_right_image = extract_box_reference(game_screen, m_dialog_right_box); - - //Menu background is teal - ImageViewRGB32 menu_background_image = extract_box_reference(game_screen, m_page_background_box); - - if (is_white(dialog_right_image) - && is_solid(menu_background_image, { 0.020, 0.424, 0.412 }, 0.25, 20) //5, 108, 105 teal - ){ - return true; - } - return false; -} ImageFloatBox PartySlotDetector::party_slot_boxes(PartySlot position){ switch (position){ diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h index 74ab616a60..61dfb64d33 100644 --- a/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h +++ b/SerialPrograms/Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h @@ -51,26 +51,6 @@ class PartyMenuWatcher : public DetectorToFinder{ {} }; -// The Party menu has a white box in the bottom right corner when a Pokemon is selected -// The background around the edges is dark teal/navy -class PartySelectionDetector : public StaticScreenDetector{ -public: - PartySelectionDetector(Color color); - - virtual void make_overlays(VideoOverlaySet& items) const override; - virtual bool detect(const ImageViewRGB32& screen) override; - -private: - ImageFloatBox m_dialog_right_box; - ImageFloatBox m_page_background_box; -}; -class PartySelectionWatcher : public DetectorToFinder{ -public: - PartySelectionWatcher(Color color) - : DetectorToFinder("PartySelectionWatcher", std::chrono::milliseconds(250), color) - {} -}; - class PartySlotDetector : public StaticScreenDetector{ public: diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp new file mode 100644 index 0000000000..ab2c887752 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp @@ -0,0 +1,94 @@ +/* Party Level Up Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include "Common/Cpp/Color.h" +#include "Common/Cpp/Exceptions.h" +#include "CommonFramework/GlobalSettingsPanel.h" +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/Tools/GlobalThreadPools.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/Images/ImageFilter.h" +#include "CommonTools/Images/ImageManip.h" +#include "CommonTools/OCR/OCR_NumberReader.h" +#include "CommonTools/OCR/OCR_Routines.h" +#include "Pokemon/Pokemon_StatsCalculation.h" +#include "Pokemon/Inference/Pokemon_NameReader.h" +#include "Pokemon/Inference/Pokemon_NatureReader.h" +#include "PokemonFRLG/PokemonFRLG_Settings.h" +#include "PokemonFRLG_DigitReader.h" +#include "PokemonFRLG_PartyLevelUpReader.h" + + +namespace PokemonAutomation { +namespace NintendoSwitch { +namespace PokemonFRLG { + + +PartyLevelUpReader::PartyLevelUpReader(Color color) + : m_color(color) + , m_box_hp(0.904069, 0.051852, 0.068535, 0.079387) + , m_box_attack(0.904069, 0.145052, 0.068535, 0.079387) + , m_box_defense(0.904069, 0.238252, 0.068535, 0.079387) + , m_box_sp_attack(0.904069, 0.331452, 0.068535, 0.079387) + , m_box_sp_defense(0.904069, 0.424652, 0.068535, 0.079387) + , m_box_speed(0.904069, 0.517852, 0.068535, 0.0793879) +{} + +void PartyLevelUpReader::make_overlays(VideoOverlaySet &items) const { + const BoxOption &GAME_BOX = GameSettings::instance().GAME_BOX; + items.add(m_color, GAME_BOX.inner_to_outer(m_box_hp)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box_attack)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box_defense)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box_sp_attack)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box_sp_defense)); + items.add(m_color, GAME_BOX.inner_to_outer(m_box_speed)); +} + +StatReads PartyLevelUpReader::read_stats(Logger &logger, const ImageViewRGB32& frame) const{ + ImageViewRGB32 game_screen = + extract_box_reference(frame, GameSettings::instance().GAME_BOX); + + auto read_stat = [&](const ImageFloatBox &box, const std::string &name){ + ImageViewRGB32 stat_region = extract_box_reference(game_screen, box); + + if (!GlobalSettings::instance().USE_PADDLE_OCR){ + // Tesseract-free path: waterfill segmentation + template matching + // against the PokemonFRLG/Digits/0-9.png templates. + return read_digits_waterfill_template(logger, stat_region); + } + + // PaddleOCR path (original): preprocess then per-digit waterfill OCR. + // Dark text [0..190] -> black. Threshold at 190 captures the + // blurred gap pixels between segments, making bridges thicker. + // Not higher than 190 to avoid capturing yellow bg edge noise. + ImageRGB32 ocr_ready = preprocess_for_ocr( + stat_region, name, 7, 2, true, + combine_rgb(0, 0, 0), combine_rgb(190, 190, 190) + ); + + // Waterfill isolates each digit -> per-char SINGLE_CHAR OCR. + return OCR::read_number_waterfill( + logger, ocr_ready, 0xff000000, + 0xff808080 + ); + }; + + StatReads stats; + stats.hp = uint16_t(read_stat(m_box_hp, "hp")); + stats.attack = uint16_t(read_stat(m_box_attack, "attack")); + stats.defense = uint16_t(read_stat(m_box_defense, "defense")); + stats.spatk = uint16_t(read_stat(m_box_sp_attack, "spatk")); + stats.spdef = uint16_t(read_stat(m_box_sp_defense, "spdef")); + stats.speed = uint16_t(read_stat(m_box_speed, "speed")); + return stats; +} + + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation + diff --git a/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h new file mode 100644 index 0000000000..a9edfd344d --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h @@ -0,0 +1,53 @@ +/* Party Level Up Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_PartyLevelUpReader_H +#define PokemonAutomation_PokemonFRLG_PartyLevelUpReader_H + +#include +#include +#include "Common/Cpp/Color.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/Language.h" +#include "Pokemon/Pokemon_StatsCalculation.h" + + +namespace PokemonAutomation{ + +class Logger; +class ImageViewRGB32; +class VideoOverlaySet; + +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +using namespace Pokemon; + + +class PartyLevelUpReader { +public: + PartyLevelUpReader(Color color = COLOR_RED); + + void make_overlays(VideoOverlaySet &items) const; + + StatReads read_stats(Logger &logger, const ImageViewRGB32& frame) const; + +private: + Color m_color; + ImageFloatBox m_box_hp; + ImageFloatBox m_box_attack; + ImageFloatBox m_box_defense; + ImageFloatBox m_box_sp_attack; + ImageFloatBox m_box_sp_defense; + ImageFloatBox m_box_speed; + +}; + +} // namespace PokemonFRLG +} // namespace NintendoSwitch +} // namespace PokemonAutomation +#endif + diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp index d8d9f24d3a..04d8d05bf5 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.cpp @@ -20,11 +20,13 @@ #include "PokemonFRLG/PokemonFRLG_Settings.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h" #include "PokemonFRLG/Inference/Sounds/PokemonFRLG_ShinySoundDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_StartMenuDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_LoadMenuDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h" #include "PokemonFRLG/Inference/Map/PokemonFRLG_MapDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h" #include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" @@ -677,6 +679,65 @@ void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext } } +void open_bag_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context){ + uint16_t errors = 0; + bool start_menu_is_open = false; + while (true){ + if (errors > 5){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "open_party_menu_from_overworld(): Failed to open party menu 5 times in a row.", + console + ); + } + + context.wait_for_all_requests(); + if (!start_menu_is_open){ + open_start_menu(console, context); // This is unavoidable since we cannot detect the overworld. + start_menu_is_open = true; + } + + StartMenuWatcher start_menu(COLOR_RED); + BagWatcher bag(COLOR_RED); + + int ret = wait_until( + console, context, 10000ms, + { start_menu, bag } + ); + + switch (ret){ + case 0: + if (menu_context == StartMenuContext::SAFARI_ZONE){ + ret = move_cursor_to_position(console, context, SelectionArrowPositionSafariMenu::BAG); + } else { + ret = move_cursor_to_position(console, context, SelectionArrowPositionStartMenu::BAG); + } + + if (ret < 0){ + console.log("Failed to navigate to BAG on the start menu."); + errors++; + context.wait_for_all_requests(); + pbf_mash_button(context, BUTTON_B, 2000ms); + start_menu_is_open = false; + } else { + console.log("Navigated to BAG on the start menu"); + context.wait_for_all_requests(); + pbf_press_button(context, BUTTON_A, 200ms, 1300ms); + } + continue; + case 1: + console.log("Bag opened."); + return; + default: + console.log("Failed to open bag."); + errors++; + pbf_mash_button(context, BUTTON_B, 2000ms); + start_menu_is_open = false; + continue; + } + } +} + void use_teleport_from_overworld(ConsoleHandle& console, ProControllerContext& context){ uint16_t errors = 0; diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h index 3f8ee0f6fc..c984dfb6ff 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Navigation.h @@ -76,6 +76,9 @@ enum class StartMenuContext { }; void open_party_menu_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context = StartMenuContext::STANDARD); +// Starting from the start menu, a sub-screen of the start menu, or the overworld, navigate to the bag +void open_bag_from_overworld(ConsoleHandle& console, ProControllerContext& context, StartMenuContext menu_context = StartMenuContext::STANDARD); + // Uses Teleport to return to a PokeCenter. // Assumes that Teleport is usable and the last party member has it learned void use_teleport_from_overworld(ConsoleHandle& console, ProControllerContext& context); diff --git a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp index 05aba7c391..01bb6accff 100644 --- a/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp +++ b/SerialPrograms/Source/PokemonFRLG/PokemonFRLG_Panels.cpp @@ -23,6 +23,7 @@ #include "Programs/RngManipulation/PokemonFRLG_RngHelper.h" #include "Programs/RngManipulation/PokemonFRLG_SidHelper.h" #include "Programs/RngManipulation/PokemonFRLG_StarterRng.h" +#include "Programs/RngManipulation/PokemonFRLG_GiftRng.h" #include "Programs/TestPrograms/PokemonFRLG_SoundListener.h" #include "Programs/TestPrograms/PokemonFRLG_ReadStats.h" #include "Programs/TestPrograms/PokemonFRLG_ReadBattleLevelUp.h" @@ -69,6 +70,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()); } if (PreloadSettings::instance().DEVELOPER_MODE){ diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_EvTrainer.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_EvTrainer.cpp index 7092392528..a3212a04cb 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_EvTrainer.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_EvTrainer.cpp @@ -19,6 +19,7 @@ #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h" #include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" #include "Pokemon/Inference/Pokemon_NameReader.h" diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_PickupFarmer.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_PickupFarmer.cpp index 75ed895b7f..9f93faa7d7 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_PickupFarmer.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/Farming/PokemonFRLG_PickupFarmer.cpp @@ -15,6 +15,7 @@ #include "Pokemon/Pokemon_Strings.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h" #include "PokemonFRLG/Programs/PokemonFRLG_StartMenuNavigation.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp index 8f42bd5295..a37061114b 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp @@ -392,6 +392,8 @@ void check_timings( ); } return; + case PokemonFRLG_RngTarget::hitmonchan: + case PokemonFRLG_RngTarget::hitmonlee: case PokemonFRLG_RngTarget::hitmon: if (INGAME_DELAY < 4500){ OperationFailedException::fire( @@ -419,6 +421,9 @@ void check_timings( ); } return; + case PokemonFRLG_RngTarget::omanyte: + case PokemonFRLG_RngTarget::kabuto: + case PokemonFRLG_RngTarget::aerodactyl: case PokemonFRLG_RngTarget::fossils: if (INGAME_DELAY < 6000){ OperationFailedException::fire( @@ -431,6 +436,8 @@ void check_timings( case PokemonFRLG_RngTarget::gamecornerabra: case PokemonFRLG_RngTarget::gamecornerclefairy: case PokemonFRLG_RngTarget::gamecornerdratini: + case PokemonFRLG_RngTarget::gamecornerscyther: + case PokemonFRLG_RngTarget::gamecornerpinsir: case PokemonFRLG_RngTarget::gamecornerbug: case PokemonFRLG_RngTarget::gamecornerporygon: if (INGAME_DELAY < 8500){ @@ -608,6 +615,8 @@ void perform_blind_sequence( case PokemonFRLG_RngTarget::magikarp: collect_magikarp_after_delay(context, INGAME_DELAY); return; + case PokemonFRLG_RngTarget::hitmonchan: + case PokemonFRLG_RngTarget::hitmonlee: case PokemonFRLG_RngTarget::hitmon: collect_hitmon_after_delay(context, INGAME_DELAY); return; @@ -617,6 +626,9 @@ void perform_blind_sequence( case PokemonFRLG_RngTarget::lapras: collect_lapras_after_delay(context, INGAME_DELAY); return; + case PokemonFRLG_RngTarget::omanyte: + case PokemonFRLG_RngTarget::kabuto: + case PokemonFRLG_RngTarget::aerodactyl: case PokemonFRLG_RngTarget::fossils: collect_fossil_after_delay(context, INGAME_DELAY); return; @@ -629,6 +641,8 @@ void perform_blind_sequence( case PokemonFRLG_RngTarget::gamecornerdratini: collect_gamecorner_after_delay(context, INGAME_DELAY, 2); return; + case PokemonFRLG_RngTarget::gamecornerscyther: + case PokemonFRLG_RngTarget::gamecornerpinsir: case PokemonFRLG_RngTarget::gamecornerbug: collect_gamecorner_after_delay(context, INGAME_DELAY, 3); return; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h index 338c0e12fa..8f3dd0c8d0 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.h @@ -21,13 +21,20 @@ namespace PokemonFRLG{ starters, magikarp, hitmon, + hitmonchan, + hitmonlee, eevee, lapras, fossils, + omanyte, + kabuto, + aerodactyl, gamecornerabra, gamecornerclefairy, gamecornerdratini, gamecornerbug, + gamecornerscyther, + gamecornerpinsir, gamecornerporygon, togepi, staticencounter, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp new file mode 100644 index 0000000000..f51d55a761 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -0,0 +1,688 @@ +/* Gift RNG + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include +#include "CommonTools/Random.h" +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/Notifications/ProgramNotifications.h" +#include "CommonFramework/ProgramStats/StatsTracking.h" +#include "CommonFramework/VideoPipeline/VideoFeed.h" +#include "CommonTools/Async/InferenceRoutines.h" +#include "CommonTools/StartupChecks/StartProgramChecks.h" +#include "Pokemon/Pokemon_Strings.h" +#include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" +#include "NintendoSwitch/NintendoSwitch_Settings.h" +#include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" +#include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_PartyMenuDetector.h" +#include "PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h" +#include "PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h" +#include "PokemonFRLG/Inference/PokemonFRLG_StatsReader.h" +#include "PokemonFRLG/PokemonFRLG_Navigation.h" +#include "PokemonFRLG_BlindNavigation.h" +#include "PokemonFRLG_RngNavigation.h" +#include "PokemonFRLG_HardReset.h" +#include "PokemonFRLG_GiftRng.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + + +GiftRng_Descriptor::GiftRng_Descriptor() + : SingleSwitchProgramDescriptor( + "PokemonFRLG:GiftRng", + Pokemon::STRING_POKEMON + " FRLG", "Gift RNG", + "Programs/PokemonFRLG/GiftRng.html", + "Automatically calibrate timings to hit a specific RNG target for FRLG starters.", + ProgramControllerClass::StandardController_RequiresPrecision, + FeedbackType::REQUIRED, + AllowCommandsWhenRunning::DISABLE_COMMANDS + ) +{} + +struct GiftRng_Descriptor::Stats : public StatsTracker{ + Stats() + : resets(m_stats["Resets"]) + , shinies(m_stats["Shinies"]) + , nonshiny(m_stats["Non-Shiny Hits"]) + , errors(m_stats["Errors"]) + { + m_display_order.emplace_back("Resets"); + m_display_order.emplace_back("Shinies"); + m_display_order.emplace_back("Non-Shiny Hits", HIDDEN_IF_ZERO); + m_display_order.emplace_back("Errors", HIDDEN_IF_ZERO); + } + std::atomic& resets; + std::atomic& shinies; + std::atomic& nonshiny; + std::atomic& errors; +}; +std::unique_ptr GiftRng_Descriptor::make_stats() const{ + return std::unique_ptr(new Stats()); +} + +GiftRng::GiftRng() + : LANGUAGE( + "Game Language:", + { + Language::English, + Language::Japanese, + Language::Spanish, + Language::French, + Language::German, + Language::Italian, + }, + LockMode::LOCK_WHILE_RUNNING, + true + ) + , TARGET( + "Target:
", + { + {PokemonFRLG_RngTarget::magikarp, "magikarp", "Magikarp"}, + {PokemonFRLG_RngTarget::hitmonchan, "hitmonchan", "Hitmonchan"}, + {PokemonFRLG_RngTarget::hitmonlee, "hitmonlee", "Hitmonlee"}, + {PokemonFRLG_RngTarget::eevee, "eevee", "Eevee"}, + {PokemonFRLG_RngTarget::lapras, "lapras", "Lapras"}, + {PokemonFRLG_RngTarget::omanyte, "omanyte", "Omanyte"}, + {PokemonFRLG_RngTarget::kabuto, "kabuto", "Kabuto"}, + {PokemonFRLG_RngTarget::aerodactyl, "aerodactyl", "Aerodactyl"}, + {PokemonFRLG_RngTarget::gamecornerabra, "gamecornerabra", "Game Corner Abra"}, + {PokemonFRLG_RngTarget::gamecornerclefairy, "gamecornerclefairy", "Game Corner Clefairy"}, + {PokemonFRLG_RngTarget::gamecornerdratini, "gamecornerdratini", "Game Corner Dratini"}, + {PokemonFRLG_RngTarget::gamecornerscyther, "gamecornerscyther", "Game Corner Scyther"}, + {PokemonFRLG_RngTarget::gamecornerpinsir, "gamecornerpinsir", "Game Corner Pinsir"}, + {PokemonFRLG_RngTarget::gamecornerporygon, "gamecornerporygon", "Game Corner Porygon"}, + {PokemonFRLG_RngTarget::togepi, "togepi", "Togepi"}, + }, + LockMode::LOCK_WHILE_RUNNING, + PokemonFRLG_RngTarget::magikarp + ) + , MAX_RESETS( + "Max Resets:
", + LockMode::UNLOCK_WHILE_RUNNING, + 50, 0 // default, min + ) + , MAX_RARE_CANDIES( + "Max Rare Candies:
" + "The number of rare candies in your bag. Make sure these are at the top position of the bag.
" + "Rare candies used during calibration will be restored after resetting.", + LockMode::UNLOCK_WHILE_RUNNING, + 0, 0, 999 // default, min, max + ) + , SEED( + false, + "Target Seed:", + LockMode::LOCK_WHILE_RUNNING, + "70FE", "70FE", + true + ) + , SEED_LIST( + "Nearby Seeds:
" + "This box should contain a list of seeds (in order) around and including your target seed, with one seed on each line", + LockMode::LOCK_WHILE_RUNNING, + "D000\n199A\n77A1\nAABC\n280C\n70FE\nB573\n02F2\n8084\nA533\nED1E", + "D000\n199A\n77A1\nAABC\n280C\n70FE\nB573\n02F2\n8084\nA533\nED1E", + true + ) + , SEED_BUTTON( + "Seed Button:
", + { + {SeedButton::A, "A", "A"}, + {SeedButton::Start, "Start", "Start"}, + {SeedButton::L, "L", "L (L=A)"}, + }, + LockMode::LOCK_WHILE_RUNNING, + SeedButton::A + ) + , EXTRA_BUTTON( + "Extra Button:
" + "Additional button presses that affect the seed.", + { + {BlackoutButton::None, "None", "None"}, + {BlackoutButton::L, "L", "Blackout L"}, + {BlackoutButton::R, "R", "Blackout R"}, + }, + LockMode::LOCK_WHILE_RUNNING, + BlackoutButton::None + ) + , SEED_DELAY( + "Seed Delay Time (ms):
The delay between starting the game and advancing past the title screen. Set this to match your target seed.", + LockMode::LOCK_WHILE_RUNNING, + 31338, 28000 // default, min + ) + , ADVANCES( + "Advances:
The total number of RNG advances for your target.
This should be the combined amount of continue screen and in-game advances.", + LockMode::LOCK_WHILE_RUNNING, + 10000, 600, 1000000000 // default, min + ) + // , CONTINUE_SCREEN_FRAMES( + // "Continue Screen Frames:
The number of RNG advances to pass on the continue screen.
This should be less than the total number of advances above.", + // LockMode::LOCK_WHILE_RUNNING, + // 1000, 192 // default, min + // ) + , USE_TEACHY_TV( + "Use Teachy TV:" + "
Opens the Teachy TV to quickly advance the RNG at 313x speed.
" + "Warning: can result in larger misses.", + LockMode::LOCK_WHILE_RUNNING, + false // default + ) + , PROFILE( + "User Profile Position:
" + "The position, from left to right, of the Switch profile with the FRLG save you'd like to use.
" + "If this is set to 0, Switch 1 defaults to the last-used profile, while Switch 2 defaults to the first profile (position 1)", + LockMode::LOCK_WHILE_RUNNING, + 0, 0, 8 // default, min, max + ) + , TAKE_VIDEO( + "Take Video:
Record a video when the shiny is found.", + LockMode::LOCK_WHILE_RUNNING, + true // default + ) + , GO_HOME_WHEN_DONE(true) + , 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(RNG_FILTERS); + PA_ADD_OPTION(POSSIBLE_HITS); + PA_ADD_OPTION(LANGUAGE); + PA_ADD_OPTION(TARGET); + PA_ADD_OPTION(MAX_RESETS); + PA_ADD_OPTION(MAX_RARE_CANDIES); + PA_ADD_OPTION(SEED); + PA_ADD_OPTION(SEED_LIST); + PA_ADD_OPTION(SEED_BUTTON); + PA_ADD_OPTION(EXTRA_BUTTON); + PA_ADD_OPTION(SEED_DELAY); + PA_ADD_OPTION(ADVANCES); + // PA_ADD_OPTION(CONTINUE_SCREEN_FRAMES); + PA_ADD_OPTION(USE_TEACHY_TV); + PA_ADD_OPTION(PROFILE); + PA_ADD_OPTION(TAKE_VIDEO); + PA_ADD_OPTION(GO_HOME_WHEN_DONE); + PA_ADD_OPTION(NOTIFICATIONS); +} + + + +bool GiftRng::have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit){ + return (hit.seed == TARGET_SEED) && (hit.advance == ADVANCES); +} + +AdvObservedPokemon GiftRng::read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + // assumes we're already on the first summary page + PokemonFRLG_Stats stats; + StatsReader reader(COLOR_RED); + + env.log("Reading Page 1 (Name, Level, Nature, Gender)..."); + VideoSnapshot screen1 = env.console.video().snapshot(); + reader.read_page1(env.logger(), LANGUAGE, screen1, stats); + + SummaryPage2Watcher page_two(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<5; i++){ + pbf_press_dpad(context, DPAD_RIGHT, 200ms, 1800ms); + } + }, + { page_two } + ); + + if (ret < 0){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "read_summary(): Failed to detect second summary screen.", + env.console + ); + } + + env.log("Reading Page 2 (Stats)..."); + VideoSnapshot screen2 = env.console.video().snapshot(); + reader.read_page2(env.logger(), screen2, stats); + + StatReads statreads = { + static_cast(stats.hp.value_or(0)), + static_cast(stats.attack.value_or(0)), + static_cast(stats.defense.value_or(0)), + static_cast(stats.sp_attack.value_or(0)), + static_cast(stats.sp_defense.value_or(0)), + static_cast(stats.speed.value_or(0)) + }; + + AdvGender gender; + switch(stats.gender.value_or(SummaryGender::Genderless)){ + case SummaryGender::Male: + gender = AdvGender::Male; + break; + case SummaryGender::Female: + gender = AdvGender::Female; + break; + default: + gender = AdvGender::Any; + break; + } + + AdvObservedPokemon pokemon = { + gender, + string_to_nature(stats.nature), + AdvAbility::Any, + { uint8_t(stats.level.value_or(5)) }, + { statreads }, + { {0,0,0,0,0,0} }, + AdvShinyType::Any + }; + + return pokemon; +} + +bool GiftRng::use_rare_candy( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const BaseStats& BASE_STATS, + bool first +){ + // navigate to the bag (only needed for the first use) + if (first){ + open_bag_from_overworld(env.console, context); + // move left to the correct pocket (in case Teachy TV was used) + pbf_move_left_joystick(context, {-1, 0}, 200ms, 800ms); + pbf_move_left_joystick(context, {-1, 0}, 200ms, 800ms); + } + + // use rare candy and watch for the party screen + PartyMenuWatcher party_menu(COLOR_RED); + context.wait_for_all_requests(); + int ret = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<5; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 2800ms); + } + }, + { party_menu } + ); + if (ret < 0){ + env.log("use_rare_candy(): failed to detect party menu."); + return true; + } + + // select the last party slot (unknown how full the party is, so we can't detect a particular slot) + // only needed on the first use + if (first){ + context.wait_for_all_requests(); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + pbf_move_left_joystick(context, {0, +1}, 200ms, 300ms); + } + + // watch for level up stats + PartyLevelUpWatcher level_up(COLOR_RED, PartyLevelUpDialog::stats); + context.wait_for_all_requests(); + int ret2 = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<30; i++){ + pbf_press_button(context, BUTTON_A, 200ms, 800ms); + } + }, + { level_up } + ); + if (ret2 < 0){ + env.log("use_rare_candy(): failed to detect level-up stats."); + return true; + } + + PartyLevelUpReader reader(COLOR_RED); + VideoOverlaySet overlays(env.console.overlay()); + reader.make_overlays(overlays); + + env.log("Reading stats..."); + VideoSnapshot screen = env.console.video().snapshot(); + StatReads stats = reader.read_stats(env.logger(), screen); + + update_filters(filters, pokemon, stats, {}, BASE_STATS); + RNG_FILTERS.set(filters); + + // return to the bag (possibly learning a move, but trying to preven evolution) + int attempts = 0; + while (true){ + if (attempts > 5){ + env.log("use_rare_candy(): failed to return to bag menu in 5 attempts."); + return true; + } + BagWatcher bag_menu(COLOR_RED); + PartyMoveLearnWatcher move_learn(COLOR_RED); + context.wait_for_all_requests(); + int ret3 = run_until( + env.console, context, + [](ProControllerContext& context) { + for (int i=0; i<15; i++){ + pbf_press_button(context, BUTTON_B, 200ms, 1800ms); + } + }, + { bag_menu, move_learn } + ); + attempts++; + switch (ret3){ + case 0: + env.log("Returned to bag."); + return false; + case 1: + env.log("Move learn opportunity detected."); + // don't learn move + pbf_press_button(context, BUTTON_B, 200ms, 1800ms); + pbf_press_button(context, BUTTON_A, 200ms, 1800ms); + continue; + default: + env.log("use_rare_candy(): failed to return to bag menu."); + return true; + } + } +} + + +void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ + /* + * Settings: Text Speed fast + */ + + GiftRng_Descriptor::Stats& stats = env.current_stats(); + + home_black_border_check(env.console, context); + + RNG_FILTERS.reset(); + POSSIBLE_HITS.reset(); + + const uint16_t TARGET_SEED = parse_seed(env.console, SEED); + const std::vector SEED_VALUES = parse_seed_list(env.console, SEED_LIST); + const int16_t SEED_POSITION = seed_position_in_list(TARGET_SEED, SEED_VALUES); + + if (SEED_POSITION == -1){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "GiftRng(): Target Seed is missing from the list of nearby seeds.", + env.console + ); + } + + env.log("Target Seed Value: " + std::to_string(TARGET_SEED)); + + BaseStats BASE_STATS; + switch (TARGET){ + case PokemonFRLG_RngTarget::magikarp: + BASE_STATS = { 20, 10, 55, 15, 20, 80 }; + break; + case PokemonFRLG_RngTarget::hitmonchan: + BASE_STATS = { 50, 105, 79, 35, 110, 76 }; + break; + case PokemonFRLG_RngTarget::hitmonlee: + BASE_STATS = { 50, 120, 53, 35, 110, 87 }; + break; + case PokemonFRLG_RngTarget::eevee: + BASE_STATS = { 55, 55, 50, 45, 65, 55 }; + break; + case PokemonFRLG_RngTarget::lapras: + BASE_STATS = { 130, 85, 80, 85, 95, 60 }; + break; + case PokemonFRLG_RngTarget::omanyte: + BASE_STATS = { 35, 40, 100, 90, 55, 35 }; + break; + case PokemonFRLG_RngTarget::kabuto: + BASE_STATS = { 30, 80, 90, 55, 45, 55 }; + break; + case PokemonFRLG_RngTarget::aerodactyl: + BASE_STATS = { 80, 105, 65, 60, 75, 130 }; + break; + case PokemonFRLG_RngTarget::gamecornerabra: + BASE_STATS = { 25, 20, 15, 105, 55, 90 }; + break; + case PokemonFRLG_RngTarget::gamecornerclefairy: + BASE_STATS = { 70, 45, 48, 60, 65, 35 }; + break; + case PokemonFRLG_RngTarget::gamecornerdratini: + BASE_STATS = { 41, 64, 45, 50, 50, 50 }; + break; + case PokemonFRLG_RngTarget::gamecornerscyther: + BASE_STATS = { 70, 110, 80, 55, 80, 105 }; + break; + case PokemonFRLG_RngTarget::gamecornerpinsir: + BASE_STATS = { 65, 125, 100, 55, 70, 85 }; + break; + case PokemonFRLG_RngTarget::gamecornerporygon: + BASE_STATS = { 65, 60, 70, 85, 75, 40 }; + break; + case PokemonFRLG_RngTarget::togepi: + BASE_STATS = { 35, 20, 65, 40, 65, 20 }; + break; + default: + break; + } + + const double FRAMERATE = 59.999977; // FPS + const double FRAME_DURATION = 1000 / FRAMERATE; + + uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 1 : 10; + double SEED_BUMPS[] = {0, 1, -1, 2, -2}; + + uint64_t CONTINUE_SCREEN_FRAMES = 200; + + const int64_t FIXED_SEED_OFFSET = -845; // milliseconds. approximate; + double SEED_CALIBRATION_FRAMES = 0; + double ADVANCES_CALIBRATION = 0; + double CONTINUE_SCREEN_ADJUSTMENT = 0; + + AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); + AdvPokemonResult target_result = searcher.generate_pokemon(); + env.log("Target IVs:"); + env.log("HP: " + std::to_string(target_result.ivs.hp)); + env.log("Atk: " + std::to_string(target_result.ivs.attack)); + env.log("Def: " + std::to_string(target_result.ivs.defense)); + env.log("SpA: " + std::to_string(target_result.ivs.spatk)); + env.log("SpD: " + std::to_string(target_result.ivs.spdef)); + env.log("Spe: " + std::to_string(target_result.ivs.speed)); + + RngAdvanceHistory ADVANCE_HISTORY; + RngCalibrationHistory CALIBRATION_HISTORY; + uint64_t INITIAL_ADVANCES_RADIUS = USE_TEACHY_TV ? 8192 : 1024; + uint64_t resets = 0; + bool wildshiny_found = false; + + while (true){ + if (CALIBRATION_HISTORY.results.size() > 0){ + env.log("Checking for nonshiny target hit..."); + if (have_hit_target(env, TARGET_SEED, CALIBRATION_HISTORY.results.back())){ + env.log("Target Hit!"); + stats.nonshiny++; + break; + } + env.log("Missed target."); + } + + if (resets > MAX_RESETS){ + env.log("Max resets reached."); + break; + } + + if (wildshiny_found){ + break; + } + + send_program_status_notification( + env, NOTIFICATION_STATUS_UPDATE, + "Calibrating." + ); + env.update_stats(); + + uint64_t advances_radius = INITIAL_ADVANCES_RADIUS; + for (size_t i=0; i 0){ + AdvRngState prev_hit = CALIBRATION_HISTORY.results.back(); + double prev_csf_calibration = CALIBRATION_HISTORY.continue_screen_adjustments.back(); + int64_t prev_advance_miss = int64_t(prev_hit.advance) - int64_t(ADVANCES); + if (prev_advance_miss != 0 && std::abs(prev_advance_miss) < 2){ + env.log("Attempting to correct for off-by-one miss by modifying continue screen frames."); + if (prev_advance_miss > 0){ + CONTINUE_SCREEN_ADJUSTMENT = prev_csf_calibration - 0.5; + }else{ + CONTINUE_SCREEN_ADJUSTMENT = prev_csf_calibration + 0.5; + } + CONTINUE_SCREEN_ADJUSTMENT = fmod(CONTINUE_SCREEN_ADJUSTMENT, 2); + }else{ + // we're still not that close. Slightly vary the seed to more reliably hone in on advances + double seed_bump = SEED_BUMPS[ADVANCE_HISTORY.results.size() % 5]; + SEED_CALIBRATION_FRAMES += seed_bump; + } + }else{ + double seed_bump = SEED_BUMPS[ADVANCE_HISTORY.results.size() % 5]; + SEED_CALIBRATION_FRAMES += seed_bump; + } + + double CALIBRATED_ADVANCES = ADVANCES + ADVANCES_CALIBRATION; + double INGAME_ADVANCES = CALIBRATED_ADVANCES - CONTINUE_SCREEN_FRAMES - CONTINUE_SCREEN_ADJUSTMENT; + + double TEACHY_ADVANCES = 0; + bool should_use_teachy_tv = USE_TEACHY_TV && (INGAME_ADVANCES > 5000); // don't use Teachy TV for short in-game advance targets + if (should_use_teachy_tv) { + TEACHY_ADVANCES = std::floor((INGAME_ADVANCES - 5000) / 313) * 313; + } + + env.log("Seed calibration (frames): " + std::to_string(SEED_CALIBRATION_FRAMES)); + env.log("Advance calibration (frames / 2): " + std::to_string(ADVANCES_CALIBRATION)); + env.log("Continue screen adjustment (frames): " + std::to_string(CONTINUE_SCREEN_ADJUSTMENT)); + + uint64_t CALIBRATED_SEED_DELAY = uint64_t(std::round(SEED_DELAY + FIXED_SEED_OFFSET + FRAME_DURATION * SEED_CALIBRATION_FRAMES)); + uint64_t CONTINUE_SCREEN_DELAY = uint64_t(std::round(FRAME_DURATION * (CONTINUE_SCREEN_FRAMES + CONTINUE_SCREEN_ADJUSTMENT))); + uint64_t TEACHY_DELAY = uint64_t(TEACHY_ADVANCES * FRAME_DURATION / 313); + uint64_t INGAME_DELAY = uint64_t(std::round(FRAME_DURATION * (INGAME_ADVANCES - TEACHY_ADVANCES) / 2)) - (should_use_teachy_tv ? 13700 : 0); + + env.log("Title screen duration: " + std::to_string(CALIBRATED_SEED_DELAY) + "ms"); + env.log("Continue screen duration: " + std::to_string(CONTINUE_SCREEN_DELAY) + "ms"); + if (should_use_teachy_tv){ + env.log("Teachy TV duration: " + std::to_string(TEACHY_DELAY) + "ms"); + env.log("Non-Teachy TV in-game duration: " + std::to_string(INGAME_DELAY) + "ms"); + }else{ + env.log("In-game duration: " + std::to_string(INGAME_DELAY) + "ms"); + } + + env.log("Resetting Game..."); + reset_and_perform_blind_sequence( + env.console, context, TARGET, + SEED_BUTTON, EXTRA_BUTTON, CALIBRATED_SEED_DELAY, + CONTINUE_SCREEN_DELAY, TEACHY_DELAY, INGAME_DELAY, + false, PROFILE + ); + stats.resets++; + + RNG_FILTERS.reset(); + POSSIBLE_HITS.reset(); + + bool shiny_found = check_for_shiny(env.console, context, TARGET); + + if (shiny_found){ + env.log("Shiny found!"); + stats.shinies++; + send_program_notification( + env, + NOTIFICATION_SHINY, + COLOR_YELLOW, + "Shiny found!", + {}, "", + env.console.video().snapshot(), + true + ); + if (TAKE_VIDEO){ + pbf_press_button(context, BUTTON_CAPTURE, 2000ms, 0ms); + } + break; + } + + AdvObservedPokemon pokemon = read_summary(env, context); + AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); + RNG_FILTERS.set(filters); + + std::map search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius); + POSSIBLE_HITS.set( + SEED_CALIBRATION_FRAMES * FRAME_DURATION, + CONTINUE_SCREEN_ADJUSTMENT, + ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, + search_hits + ); + bool finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 1); + if (finished || (MAX_RARE_CANDIES == 0)){ + env.log("RNG search finished."); + continue; + } + + for (int i=0; i make_stats() const override; +}; + +class GiftRng : public SingleSwitchProgramInstance{ +public: + GiftRng(); + virtual void program(SingleSwitchProgramEnvironment& env, ProControllerContext &context) override; + virtual void start_program_border_check( + VideoStream& stream, + FeedbackType feedback_type + ) override{} + +private: + enum class Gift{ + bulbasaur, + squirtle, + charmander + }; + + bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); + + AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context); + + bool use_rare_candy( + SingleSwitchProgramEnvironment& env, + ProControllerContext& context, + AdvObservedPokemon& pokemon, + AdvRngFilters& filters, + const BaseStats& BASE_STATS, + bool first + ); + + OCR::LanguageOCROption LANGUAGE; + + EnumDropdownOption TARGET; + + SimpleIntegerOption MAX_RESETS; + SimpleIntegerOption MAX_RARE_CANDIES; + + RngFilterDisplay RNG_FILTERS; + RngCalibrationDisplay POSSIBLE_HITS; + + StringOption SEED; + TextEditOption SEED_LIST; + EnumDropdownOption SEED_BUTTON; + EnumDropdownOption EXTRA_BUTTON; + SimpleIntegerOption SEED_DELAY; + + SimpleIntegerOptionADVANCES; + + BooleanCheckBoxOption USE_TEACHY_TV; + + SimpleIntegerOption PROFILE; + + BooleanCheckBoxOption TAKE_VIDEO; + GoHomeWhenDoneOption GO_HOME_WHEN_DONE; + EventNotificationOption NOTIFICATION_SHINY; + EventNotificationOption NOTIFICATION_STATUS_UPDATE; + EventNotificationsOption NOTIFICATIONS; +}; + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp new file mode 100644 index 0000000000..b061f3088f --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp @@ -0,0 +1,327 @@ +/* RNG Calibration + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include "CommonFramework/Exceptions/OperationFailedException.h" +#include "PokemonFRLG_RngCalibration.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +void check_seed_validity(ConsoleHandle& console, std::string seed_string){ + static const std::map MAP{ + {'1', '1'}, + {'2', '2'}, + {'3', '3'}, + {'4', '4'}, + {'5', '5'}, + {'6', '6'}, + {'7', '7'}, + {'8', '8'}, + {'9', '9'}, + {'0', '0'}, + {'A', 'A'}, {'a', 'A'}, + {'B', 'B'}, {'b', 'B'}, + {'C', 'C'}, {'c', 'C'}, + {'D', 'D'}, {'d', 'D'}, + {'E', 'E'}, {'e', 'E'}, + {'F', 'F'}, {'f', 'F'} + }; + + if (seed_string.size() != 4){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "GiftRng(): Invalid seed length. Seeds should be 4 characters.", + console + ); + } + + for (char ch : seed_string){ + auto iter = MAP.find(ch); + if (iter == MAP.end()){ + OperationFailedException::fire( + ErrorReport::SEND_ERROR_REPORT, + "GiftRng(): Invalid seed character. Seeds should be hex strings (valid characters are 0-9 and A-F).", + console + ); + } + } +} + +uint16_t parse_seed(ConsoleHandle& console, std::string seed_string){ + check_seed_validity(console, seed_string); + std::istringstream converter(seed_string); + uint16_t value; + converter >> std::hex >> value; + return value; +} + +std::vector parse_seed_list(ConsoleHandle& console, std::string seed_list_string){ + std::vector values; + auto ss = std::stringstream{seed_list_string}; + for (std::string line; std::getline(ss, line, '\n');){ + values.push_back(parse_seed(console, line)); + } + return values; +} + +int16_t seed_position_in_list(uint16_t seed, std::vector list){ + for (size_t i=0; i get_search_results( + ConsoleHandle& console, + AdvRngSearcher& searcher, + AdvRngFilters& filters, + const std::vector& SEED_VALUES, + const uint64_t& ADVANCES, + const uint64_t& advances_radius +){ + std::map search_hits; + for (int i=0; i<4; i++){ + uint64_t adv_radius = advances_radius * (uint64_t(1) << i); + uint64_t min_adv = ADVANCES - std::min(uint64_t(ADVANCES), adv_radius); + uint64_t max_adv = ADVANCES + adv_radius; + search_hits = searcher.search(filters, SEED_VALUES, min_adv, max_adv, 0, 30); + if (search_hits.size() > 0){ + console.log("Number of search hits: " + std::to_string(search_hits.size())); + return search_hits; + } + } + console.log("Number of search hits: " + std::to_string(search_hits.size())); + return search_hits; +} + +void update_filters( + AdvRngFilters& filters, + AdvObservedPokemon& pokemon, + const StatReads& stats, + const EVs& evyield, + const BaseStats& BASE_STATS +){ + pokemon.level.emplace_back(pokemon.level.back() + 1); + pokemon.stats.emplace_back(stats); + pokemon.evs.emplace_back(evyield); + + AdvRngFilters new_filters = observation_to_filters(pokemon, BASE_STATS); + filters.ivs = new_filters.ivs; +} + +double get_seed_calibration_frames( + const RngCalibrationHistory& HISTORY, + const std::vector& SEED_VALUES, + const int16_t& SEED_POSITION +){ + double sum = 0; + uint16_t len = 0; + for (size_t i=0; i& search_hits, + uint32_t max_advance_possibilities, + uint32_t advance_radius, + bool force_finish +){ + if (search_hits.size() == 0){ + console.log("No matches found."); + return true; + } + + if (!force_finish && search_hits.size() > max_advance_possibilities){ + return false; + } + + if (search_hits.size() == 1){ + console.log("Updating calibrations..."); + CALIBRATION_HISTORY.seed_calibrations.emplace_back(SEED_CALIBRATION_FRAMES); + CALIBRATION_HISTORY.advance_calibrations.emplace_back(ADVANCES_CALIBRATION); + CALIBRATION_HISTORY.continue_screen_adjustments.emplace_back(CONTINUE_SCREEN_ADJUSTMENT); + CALIBRATION_HISTORY.results.emplace_back(search_hits.begin()->first); + if (CALIBRATION_HISTORY.results.size() > MAX_HISTORY_LENGTH){ + CALIBRATION_HISTORY.seed_calibrations.erase(CALIBRATION_HISTORY.seed_calibrations.begin()); + CALIBRATION_HISTORY.advance_calibrations.erase(CALIBRATION_HISTORY.advance_calibrations.begin()); + CALIBRATION_HISTORY.continue_screen_adjustments.erase(CALIBRATION_HISTORY.continue_screen_adjustments.begin()); + CALIBRATION_HISTORY.results.erase(CALIBRATION_HISTORY.results.begin()); + } + ADVANCE_HISTORY.results.clear(); + ADVANCE_HISTORY.seed_calibrations.clear(); + return true; + } + + std::vector advances; + std::vector hits; + for(std::map::const_iterator it=search_hits.begin(); it!=search_hits.end(); ++it) { + advances.emplace_back(it->first.advance); + hits.emplace_back(it->first); + } + + // get unique advances + std::sort(advances.begin(), advances.end()); + std::vector::iterator iter; + iter = std::unique(advances.begin(), advances.begin() + advances.size()); + advances.resize(std::distance(advances.begin(), iter)); + + ADVANCE_HISTORY.seed_calibrations.emplace_back(SEED_CALIBRATION_FRAMES); + ADVANCE_HISTORY.results.emplace_back(hits); + + // check advance history for repeated values + std::vector counts; + uint64_t best = 0; + uint64_t mode = 0; + bool tie = false; + for (uint64_t& adv : advances){ + uint64_t count = 0; + for (auto& res : ADVANCE_HISTORY.results){ + for (auto& state : res){ + if (std::abs(int64_t(state.advance) - int64_t(adv)) <= advance_radius){ + count++; + break; // only count one possible hit from each attempt + } + } + } + if (count > best){ + mode = adv; + best = count; + tie = false; + }else if (count == best){ + tie = true; + } + } + + if (tie){ + console.log("More than 1 possible advances value hit."); + return true; + } + + + // add the closest possibility to the advances mode for each attempt to the calibration history + console.log("Inferred hits from previous " + std::to_string(ADVANCE_HISTORY.results.size()) + " attempts: "); + for (size_t i=0; i MAX_HISTORY_LENGTH){ + CALIBRATION_HISTORY.seed_calibrations.erase(CALIBRATION_HISTORY.seed_calibrations.begin()); + CALIBRATION_HISTORY.advance_calibrations.erase(CALIBRATION_HISTORY.advance_calibrations.begin()); + CALIBRATION_HISTORY.continue_screen_adjustments.erase(CALIBRATION_HISTORY.continue_screen_adjustments.begin()); + CALIBRATION_HISTORY.results.erase(CALIBRATION_HISTORY.results.begin()); + } + + ADVANCE_HISTORY.results.clear(); + ADVANCE_HISTORY.seed_calibrations.clear(); + + return true; +} + + + + +} +} +} diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h new file mode 100644 index 0000000000..484ad8d151 --- /dev/null +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h @@ -0,0 +1,91 @@ +/* RNG Calibration + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonFRLG_RngCalibration_H +#define PokemonAutomation_PokemonFRLG_RngCalibration_H + +#include +#include +#include "NintendoSwitch/NintendoSwitch_ConsoleHandle.h" +#include "Pokemon/Pokemon_AdvRng.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonFRLG{ + +using namespace Pokemon; + + +struct RngAdvanceHistory{ + std::vector seed_calibrations; + std::vector> results; +}; + +struct RngCalibrationHistory{ + std::vector seed_calibrations; + std::vector advance_calibrations; + std::vector continue_screen_adjustments; + std::vector results; +}; + + +void check_seed_validity(ConsoleHandle& console, std::string seed_string); +uint16_t parse_seed(ConsoleHandle& console, std::string seed_string); +std::vector parse_seed_list(ConsoleHandle& console, std::string seed_list_string); +int16_t seed_position_in_list(uint16_t seed, std::vector list); + +Pokemon::AdvNature string_to_nature(std::string nature_string); + +// get search hits for any of the provided seed values and advances range +std::map get_search_results( + ConsoleHandle& console, + AdvRngSearcher& searcher, + AdvRngFilters& filters, + const std::vector& SEED_VALUES, + const uint64_t& ADVANCES, + const uint64_t& advances_radius +); + +// update IV ranges for RNG search filters with new stats/EVs after leveling up +void update_filters( + AdvRngFilters& filters, + AdvObservedPokemon& pokemon, + const StatReads& stats, + const EVs& evyield, + const BaseStats& BASE_STATS +); + +// get seed calibration based on average offset in the RNG calibration history +double get_seed_calibration_frames( + const RngCalibrationHistory& HISTORY, + const std::vector& SEED_VALUES, + const int16_t& SEED_POSITION +); + +// get advances calibration based on average offset in the RNG calibration history +double get_advances_calibration_frames(const RngCalibrationHistory& CALIBRATION_HISTORY, const uint64_t& ADVANCES); + +// infer hit seeds/advances, update the calibration history, and return whether or not the search is finished +bool update_history( + ConsoleHandle& console, + RngAdvanceHistory& ADVANCE_HISTORY, + RngCalibrationHistory& CALIBRATION_HISTORY, + const uint16_t& MAX_HISTORY_LENGTH, + const double& SEED_CALIBRATION_FRAMES, + const double& ADVANCES_CALIBRATION, + const double& CONTINUE_SCREEN_ADJUSTMENT, + const std::map& search_hits, + uint32_t max_advance_possibilities = 1, + uint32_t advance_radius = 2, + bool force_finish = false +); + + + +} +} +} +#endif diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp index 64bc3f4ff8..79ee04b6ee 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp @@ -155,14 +155,20 @@ void RngFilterDisplay::reset(){ nature.set("-"); } -PossibleHitsDisplay::PossibleHitsDisplay() - : GroupOption("Possible Hits", LockMode::READ_ONLY) +RngCalibrationDisplay::RngCalibrationDisplay() + : GroupOption("RNG Calibration", LockMode::READ_ONLY) + , seed_calibration(false, "Seed Calibration (ms):", LockMode::READ_ONLY, "-", "") + , csf_calibration(false, "Continue Screen Frames Calibration:", LockMode::READ_ONLY, "-", "") + , advances_calibration(false, "In-Game Advances Calibration:", LockMode::READ_ONLY, "-", "") , hits(false, "Seeds/Advances:", LockMode::READ_ONLY, "-", "") { + PA_ADD_STATIC(seed_calibration); + PA_ADD_STATIC(csf_calibration); + PA_ADD_STATIC(advances_calibration); PA_ADD_STATIC(hits); } -std::vector PossibleHitsDisplay::get_rng_states_from_map(const std::map& hits_map){ +std::vector RngCalibrationDisplay::get_rng_states_from_map(const std::map& hits_map){ std::vector rng_states; for(std::map::const_iterator it = hits_map.begin(); it != hits_map.end(); ++it) { rng_states.emplace_back(it->first); @@ -170,7 +176,7 @@ std::vector PossibleHitsDisplay::get_rng_states_from_map(const std: return rng_states; } -std::string PossibleHitsDisplay::get_hits_string(const std::vector& rng_states){ +std::string RngCalibrationDisplay::get_hits_string(const std::vector& rng_states){ std::string hits_string; for (size_t i=0; i 0){ @@ -189,19 +195,35 @@ std::string PossibleHitsDisplay::get_hits_string(const std::vector& } return hits_string; } -std::string PossibleHitsDisplay::get_hits_string(const std::map& hits_map){ +std::string RngCalibrationDisplay::get_hits_string(const std::map& hits_map){ return get_hits_string(get_rng_states_from_map(hits_map)); } -void PossibleHitsDisplay::set(const std::vector& rng_states){ +void RngCalibrationDisplay::set( + double s_calibration, + double c_calibration, + double a_calibration, + std::vector& rng_states +){ + seed_calibration.set(std::to_string(s_calibration)); + csf_calibration.set(std::to_string(c_calibration)); + advances_calibration.set(std::to_string(a_calibration)); hits.set(get_hits_string(rng_states)); } -void PossibleHitsDisplay::set(const std::map& hits_map){ +void RngCalibrationDisplay::set( + double s_calibration, + double c_calibration, + double a_calibration, + const std::map& hits_map +){ std::vector rng_states = get_rng_states_from_map(hits_map); - set(rng_states); + set(s_calibration, c_calibration, a_calibration, rng_states); } -void PossibleHitsDisplay::reset(){ +void RngCalibrationDisplay::reset(){ + seed_calibration.set("-"); + csf_calibration.set("-"); + advances_calibration.set("-"); hits.set("-"); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h index 103d99143e..ada567a522 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h @@ -59,12 +59,22 @@ class RngFilterDisplay : public GroupOption{ }; -class PossibleHitsDisplay : public GroupOption{ +class RngCalibrationDisplay : public GroupOption{ public: - PossibleHitsDisplay(); - - void set(const std::vector& rng_states); - void set(const std::map& hits_map); + RngCalibrationDisplay(); + + void set( + double s_calibraiton, + double c_calibration, + double a_calibration, + std::vector& rng_states + ); + void set( + double s_calibration, + double c_calibration, + double a_calibration, + const std::map& hits_map + ); void reset(); private: @@ -72,6 +82,9 @@ class PossibleHitsDisplay : public GroupOption{ static std::string get_hits_string(const std::vector& rng_states); static std::string get_hits_string(const std::map& hits_map); public: + StringOption seed_calibration; + StringOption csf_calibration; + StringOption advances_calibration; StringOption hits; }; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index c3f43d10bc..42531db614 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -18,19 +18,15 @@ #include "CommonTools/StartupChecks/StartProgramChecks.h" #include "Pokemon/Pokemon_Strings.h" #include "Pokemon/Pokemon_StatsCalculation.h" -#include "Pokemon/Pokemon_AdvRng.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_PushButtons.h" #include "NintendoSwitch/Commands/NintendoSwitch_Commands_Superscalar.h" #include "NintendoSwitch/NintendoSwitch_Settings.h" -#include "NintendoSwitch/Inference/NintendoSwitch_HomeMenuDetector.h" -#include "NintendoSwitch/Inference/NintendoSwitch_UpdatePopupDetector.h" #include "NintendoSwitch/Programs/NintendoSwitch_GameEntry.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h" #include "PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h" #include "PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h" -#include "PokemonFRLG/Inference/PokemonFRLG_ShinySymbolDetector.h" #include "PokemonFRLG/Inference/PokemonFRLG_StatsReader.h" #include "PokemonFRLG/PokemonFRLG_Navigation.h" #include "PokemonFRLG_BlindNavigation.h" @@ -208,104 +204,6 @@ StarterRng::StarterRng() } -namespace { - -void check_seed_validity(SingleSwitchProgramEnvironment& env, std::string seed_string){ - static const std::map MAP{ - {'1', '1'}, - {'2', '2'}, - {'3', '3'}, - {'4', '4'}, - {'5', '5'}, - {'6', '6'}, - {'7', '7'}, - {'8', '8'}, - {'9', '9'}, - {'0', '0'}, - {'A', 'A'}, {'a', 'A'}, - {'B', 'B'}, {'b', 'B'}, - {'C', 'C'}, {'c', 'C'}, - {'D', 'D'}, {'d', 'D'}, - {'E', 'E'}, {'e', 'E'}, - {'F', 'F'}, {'f', 'F'} - }; - - if (seed_string.size() != 4){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "StarterRng(): Invalid seed length. Seeds should be 4 characters.", - env.console - ); - } - - for (char ch : seed_string){ - auto iter = MAP.find(ch); - if (iter == MAP.end()){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "StarterRng(): Invalid seed character. Seeds should be hex strings (valid characters are 0-9 and A-F).", - env.console - ); - } - } -} - -uint16_t parse_seed(SingleSwitchProgramEnvironment& env, std::string seed_string){ - check_seed_validity(env, seed_string); - std::istringstream converter(seed_string); - uint16_t value; - converter >> std::hex >> value; - return value; -} - -std::vector parse_seed_list(SingleSwitchProgramEnvironment& env, std::string seed_list_string){ - std::vector values; - auto ss = std::stringstream{seed_list_string}; - for (std::string line; std::getline(ss, line, '\n');){ - values.push_back(parse_seed(env, line)); - } - return values; -} - -int16_t seed_position_in_list(uint16_t seed, std::vector list){ - for (size_t i=0; i StarterRng::get_starter_search_results( - SingleSwitchProgramEnvironment& env, - AdvRngSearcher& searcher, - AdvRngFilters& filters, - const std::vector& SEED_VALUES, - const uint64_t& ADVANCES, - const uint64_t& advances_radius, - const AdvObservedPokemon& pokemon -){ - std::map search_hits; - for (int i=0; i<4; i++){ - uint64_t adv_radius = advances_radius * (uint64_t(1) << i); - uint64_t min_adv = ADVANCES - std::min(uint64_t(ADVANCES), adv_radius); - uint64_t max_adv = ADVANCES + adv_radius; - search_hits = searcher.search(filters, SEED_VALUES, min_adv, max_adv, 0, 30); - if (search_hits.size() > 0){ - env.log("Number of search hits: " + std::to_string(search_hits.size())); - POSSIBLE_HITS.set(search_hits); - return search_hits; - } - } - env.log("Number of search hits: " + std::to_string(search_hits.size())); - POSSIBLE_HITS.set(search_hits); - return search_hits; -} - -double StarterRng::get_seed_calibration_frames( - const StarterRngCalibrationHistory& HISTORY, - const std::vector& SEED_VALUES, - const int16_t& SEED_POSITION -){ - double sum = 0; - uint16_t len = 0; - for (size_t i=0; i& search_hits, - bool force_finish -){ - const int MAX_ADVANCE_POSSIBILITIES = 5; - const uint32_t ADVANCE_RADIUS = 2; - - if (search_hits.size() == 0){ - env.log("No matches found."); - return true; - } - - if (!force_finish && search_hits.size() > MAX_ADVANCE_POSSIBILITIES){ - return false; - } - - if (search_hits.size() == 1){ - env.log("Updating calibrations..."); - CALIBRATION_HISTORY.seed_calibrations.emplace_back(SEED_CALIBRATION_FRAMES); - CALIBRATION_HISTORY.advance_calibrations.emplace_back(ADVANCES_CALIBRATION); - CALIBRATION_HISTORY.continue_screen_adjustments.emplace_back(CONTINUE_SCREEN_ADJUSTMENT); - CALIBRATION_HISTORY.results.emplace_back(search_hits.begin()->first); - if (CALIBRATION_HISTORY.results.size() > MAX_HISTORY_LENGTH){ - CALIBRATION_HISTORY.seed_calibrations.erase(CALIBRATION_HISTORY.seed_calibrations.begin()); - CALIBRATION_HISTORY.advance_calibrations.erase(CALIBRATION_HISTORY.advance_calibrations.begin()); - CALIBRATION_HISTORY.continue_screen_adjustments.erase(CALIBRATION_HISTORY.continue_screen_adjustments.begin()); - CALIBRATION_HISTORY.results.erase(CALIBRATION_HISTORY.results.begin()); - } - ADVANCE_HISTORY.results.clear(); - ADVANCE_HISTORY.seed_calibrations.clear(); - return true; - } - - std::vector advances; - std::vector hits; - for(std::map::const_iterator it=search_hits.begin(); it!=search_hits.end(); ++it) { - advances.emplace_back(it->first.advance); - hits.emplace_back(it->first); - } - - // get unique advances - std::sort(advances.begin(), advances.end()); - std::vector::iterator iter; - iter = std::unique(advances.begin(), advances.begin() + advances.size()); - advances.resize(std::distance(advances.begin(), iter)); - - ADVANCE_HISTORY.seed_calibrations.emplace_back(SEED_CALIBRATION_FRAMES); - ADVANCE_HISTORY.results.emplace_back(hits); - - // check advance history for repeated values - std::vector counts; - uint64_t best = 0; - uint64_t mode = 0; - bool tie = false; - for (uint64_t& adv : advances){ - uint64_t count = 0; - for (auto& res : ADVANCE_HISTORY.results){ - for (auto& state : res){ - if (std::abs(int64_t(state.advance) - int64_t(adv)) <= ADVANCE_RADIUS){ - count++; - break; // only count one possible hit from each attempt - } - } - } - if (count > best){ - mode = adv; - best = count; - tie = false; - }else if (count == best){ - tie = true; - } - } - - if (tie){ - env.log("More than 1 possible advances value hit."); - return true; - } - - - // add the closest possibility to the advances mode for each attempt to the calibration history - env.log("Inferred hits from previous " + std::to_string(ADVANCE_HISTORY.results.size()) + " attempts: "); - for (size_t i=0; i MAX_HISTORY_LENGTH){ - CALIBRATION_HISTORY.seed_calibrations.erase(CALIBRATION_HISTORY.seed_calibrations.begin()); - CALIBRATION_HISTORY.advance_calibrations.erase(CALIBRATION_HISTORY.advance_calibrations.begin()); - CALIBRATION_HISTORY.continue_screen_adjustments.erase(CALIBRATION_HISTORY.continue_screen_adjustments.begin()); - CALIBRATION_HISTORY.results.erase(CALIBRATION_HISTORY.results.begin()); - } - - ADVANCE_HISTORY.results.clear(); - ADVANCE_HISTORY.seed_calibrations.clear(); - - return true; -} - - - bool StarterRng::walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context){ // return to the overworld pbf_mash_button(context, BUTTON_B, 5000ms); @@ -755,6 +436,7 @@ bool StarterRng::auto_battle_rival( StatReads stats = reader.read_stats(env.logger(), screen); update_filters(filters, pokemon, stats, evyield, BASE_STATS); + RNG_FILTERS.set(filters); // exit battle pbf_mash_button(context, BUTTON_B, 20s); @@ -883,6 +565,7 @@ int StarterRng::autolevel_on_route1( screen = env.console.video().snapshot(); stats = reader.read_stats(env.logger(), screen); update_filters(filters, pokemon, stats, evyield, BASE_STATS); + RNG_FILTERS.set(filters); exit_wild_battle(env.console, context, false, true); return 0; case -1: @@ -922,8 +605,8 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte RNG_FILTERS.reset(); POSSIBLE_HITS.reset(); - const uint16_t TARGET_SEED = parse_seed(env, SEED); - const std::vector SEED_VALUES = parse_seed_list(env, SEED_LIST); + const uint16_t TARGET_SEED = parse_seed(env.console, SEED); + const std::vector SEED_VALUES = parse_seed_list(env.console, SEED_LIST); const int16_t SEED_POSITION = seed_position_in_list(TARGET_SEED, SEED_VALUES); if (SEED_POSITION == -1){ @@ -974,8 +657,8 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte env.log("SpD: " + std::to_string(target_result.ivs.spdef)); env.log("Spe: " + std::to_string(target_result.ivs.speed)); - StarterRngAdvanceHistory ADVANCE_HISTORY; - StarterRngCalibrationHistory CALIBRATION_HISTORY; + RngAdvanceHistory ADVANCE_HISTORY; + RngCalibrationHistory CALIBRATION_HISTORY; uint64_t INITIAL_ADVANCES_RADIUS = 1024; uint64_t resets = 0; bool wildshiny_found = false; @@ -1030,6 +713,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte }else{ CONTINUE_SCREEN_ADJUSTMENT = prev_csf_calibration + 0.5; } + CONTINUE_SCREEN_ADJUSTMENT = fmod(CONTINUE_SCREEN_ADJUSTMENT, 2); }else{ // we're still not that close. Slightly vary the seed to more reliably hone in on advances double seed_bump = SEED_BUMPS[ADVANCE_HISTORY.results.size() % 5]; @@ -1091,8 +775,14 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); RNG_FILTERS.set(filters); - std::map search_hits = get_starter_search_results(env, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, pokemon); - bool finished = update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits); + std::map search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius); + POSSIBLE_HITS.set( + SEED_CALIBRATION_FRAMES * FRAME_DURATION, + CONTINUE_SCREEN_ADJUSTMENT, + ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, + search_hits + ); + bool finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 1); if (finished){ env.log("RNG search finished."); continue; @@ -1112,9 +802,9 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte } if (pokemon.level.size() > 1){ searcher.refine_search(search_hits, filters, 0, 30); - POSSIBLE_HITS.set(search_hits); + POSSIBLE_HITS.set(SEED_CALIBRATION_FRAMES, CONTINUE_SCREEN_ADJUSTMENT, ADVANCES_CALIBRATION, search_hits); env.log("Number of search hits: " + std::to_string(search_hits.size())); - finished = update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits); + finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 5); if (finished){ env.log("RNG search finished."); continue; @@ -1132,7 +822,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte while(true){ if (num_levels > MAX_LEVELS){ env.log("RNG search not complete after 3 level-ups."); - update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, true); + update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, true); break; } @@ -1168,9 +858,14 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte if (pokemon.level.size() > num_levels){ num_levels = pokemon.level.size(); searcher.refine_search(search_hits, filters, 0, 30); - POSSIBLE_HITS.set(search_hits); + POSSIBLE_HITS.set( + SEED_CALIBRATION_FRAMES * FRAME_DURATION, + CONTINUE_SCREEN_ADJUSTMENT, + ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, + search_hits + ); env.log("Number of search hits: " + std::to_string(search_hits.size())); - finished = update_history(env, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits); + finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 5); if (finished){ env.log("RNG search finished."); break; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h index 679edba274..1ffe034021 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -17,6 +17,7 @@ #include "NintendoSwitch/Options/NintendoSwitch_GoHomeWhenDoneOption.h" #include "Pokemon/Pokemon_StatsCalculation.h" #include "Pokemon/Pokemon_AdvRng.h" +#include "PokemonFRLG_RngCalibration.h" #include "PokemonFRLG_RngDisplays.h" namespace PokemonAutomation{ @@ -46,61 +47,10 @@ class StarterRng : public SingleSwitchProgramInstance{ charmander }; - struct StarterRngAdvanceHistory{ - std::vector seed_calibrations; - std::vector> results; - }; - - struct StarterRngCalibrationHistory{ - std::vector seed_calibrations; - std::vector advance_calibrations; - std::vector continue_screen_adjustments; - std::vector results; - - }; - bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); AdvObservedPokemon read_summary(SingleSwitchProgramEnvironment& env, ProControllerContext& context); - void update_filters( - AdvRngFilters& filters, - AdvObservedPokemon& pokemon, - const StatReads& stats, - const EVs& evyield, - const BaseStats& BASE_STATS - ); - std::map get_starter_search_results( - SingleSwitchProgramEnvironment& env, - AdvRngSearcher& searcher, - AdvRngFilters& filters, - const std::vector& SEED_VALUES, - const uint64_t& ADVANCES, - const uint64_t& advances_radius, - const AdvObservedPokemon& pokemon - ); - double get_seed_calibration_frames( - const StarterRngCalibrationHistory& CALIBRATION_HISTORY, - const std::vector& SEED_VALUES, - const int16_t& SEED_POSITION - ); - double get_advances_calibration_frames( - const StarterRngCalibrationHistory& CALIBRATION_HISTORY, - const uint64_t& ADVANCES - ); - - bool update_history( - SingleSwitchProgramEnvironment& env, - StarterRngAdvanceHistory& ADVANCE_HISTORY, - StarterRngCalibrationHistory& CALIBRATION_HISTORY, - const uint16_t& MAX_HISTORY_LENGTH, - const double& SEED_CALIBRATION_FRAMES, - const double& ADVANCES_CALIBRATION, - const double& CONTINUE_SCREEN_ADJUSTMENT, - const std::map& search_hits, - bool force_finish = false - ); - bool walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProControllerContext& context); bool auto_battle_rival( SingleSwitchProgramEnvironment& env, @@ -128,7 +78,7 @@ class StarterRng : public SingleSwitchProgramInstance{ SimpleIntegerOption MAX_RESETS; RngFilterDisplay RNG_FILTERS; - PossibleHitsDisplay POSSIBLE_HITS; + RngCalibrationDisplay POSSIBLE_HITS; StringOption SEED; TextEditOption SEED_LIST; @@ -154,6 +104,3 @@ class StarterRng : public SingleSwitchProgramInstance{ } } #endif - - - diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index ce2cc9b5b7..26b572f5f0 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1448,6 +1448,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonBDSP/Resources/PokemonBDSP_NameDatabase.h Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.cpp Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_BattleDialogs.h + Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.cpp + Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PartyDialogs.h Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.cpp Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_DialogDetector.h Source/PokemonFRLG/Inference/Dialogs/PokemonFRLG_PrizeSelectDetector.cpp @@ -1458,6 +1460,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Inference/Menus/PokemonFRLG_StartMenuDetector.h Source/PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.cpp Source/PokemonFRLG/Inference/Menus/PokemonFRLG_SummaryDetector.h + Source/PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.cpp + Source/PokemonFRLG/Inference/Menus/PokemonFRLG_BagDetector.h Source/PokemonFRLG/Inference/Menus/PokemonFRLG_LoadMenuDetector.cpp Source/PokemonFRLG/Inference/Menus/PokemonFRLG_LoadMenuDetector.h Source/PokemonFRLG/Inference/Menus/PokemonFRLG_PartyHeldItemDetector.cpp @@ -1480,6 +1484,8 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Inference/PokemonFRLG_StatsReader.h Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.cpp Source/PokemonFRLG/Inference/PokemonFRLG_BattleLevelUpReader.h + Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.cpp + Source/PokemonFRLG/Inference/PokemonFRLG_PartyLevelUpReader.h Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.cpp Source/PokemonFRLG/Inference/PokemonFRLG_TrainerIdReader.h Source/PokemonFRLG/Inference/PokemonFRLG_BattlePokemonDetector.cpp @@ -1528,12 +1534,16 @@ file(GLOB LIBRARY_SOURCES Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngHelper.h Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp + Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.cpp Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_SoundListener.h Source/PokemonFRLG/Programs/TestPrograms/PokemonFRLG_ReadStats.cpp From fb548206d10a1efc4289182be5ca4de86b3033ab Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 26 Apr 2026 15:28:43 -0500 Subject: [PATCH 02/12] fix gender threshold stuff --- .../Source/Pokemon/Pokemon_AdvRng.cpp | 22 ++++----- .../Source/Pokemon/Pokemon_AdvRng.h | 12 ++--- .../RngManipulation/PokemonFRLG_GiftRng.cpp | 42 ++++++++++++++--- .../PokemonFRLG_RngCalibration.cpp | 7 ++- .../PokemonFRLG_RngCalibration.h | 4 +- .../PokemonFRLG_StarterRng.cpp | 45 ++++++++++++++----- 6 files changed, 94 insertions(+), 38 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp index c1ae25df4d..52afc4ce83 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.cpp @@ -69,7 +69,7 @@ uint8_t gender_value_from_pid(uint32_t& pid){ return pid & 0xff; } -AdvGender gender_from_gender_value(uint8_t gender_value, uint8_t threshold){ +AdvGender gender_from_gender_value(uint8_t gender_value, int16_t threshold){ return (gender_value <= threshold) ? AdvGender::Female : AdvGender::Male; } @@ -145,7 +145,7 @@ AdvShinyType shiny_type_from_pid(uint32_t pid, uint16_t tid_xor_sid){ } -bool check_for_match(AdvPokemonResult res, AdvRngFilters target, uint16_t tid_xor_sid, uint8_t gender_threshold){ +bool check_for_match(AdvPokemonResult res, AdvRngFilters target, int16_t gender_threshold, uint16_t tid_xor_sid){ return (target.nature == AdvNature::Any || res.nature == target.nature) && (target.ability == AdvAbility::Any || res.ability == target.ability) && (target.gender == AdvGender::Any || gender_from_gender_value(res.gender, gender_threshold) == target.gender) @@ -192,8 +192,8 @@ void AdvRngSearcher::search_advance_range( AdvRngFilters& target, uint64_t min_advances, uint64_t max_advances, - uint16_t tid_xor_sid, - uint8_t gender_threshold + int16_t gender_threshold, + uint16_t tid_xor_sid ){ for (uint8_t m=0; m<3; m++){ set_state_advances(min_advances); @@ -220,7 +220,7 @@ void AdvRngSearcher::search_advance_range( for (uint64_t a=min_advances; a AdvRngSearcher::search( const std::vector& seeds, uint64_t min_advances, uint64_t max_advances, - uint16_t tid_xor_sid, - uint8_t gender_threshold + int16_t gender_threshold, + uint16_t tid_xor_sid ){ std::map hits; for (uint16_t seed : seeds){ set_seed(seed); - search_advance_range(hits, target, min_advances, max_advances, tid_xor_sid, gender_threshold); + search_advance_range(hits, target, min_advances, max_advances, gender_threshold, tid_xor_sid); } return hits; } @@ -248,13 +248,13 @@ std::map AdvRngSearcher::search( void AdvRngSearcher::refine_search( std::map& map, AdvRngFilters& target, - uint16_t tid_xor_sid, - uint8_t gender_threshold + int16_t gender_threshold, + uint16_t tid_xor_sid ){ for (auto iter = map.begin(); iter != map.end(); ){ state = iter->first; AdvPokemonResult res = pokemon_from_state(state); - if (!check_for_match(res, target, tid_xor_sid, gender_threshold)){ + if (!check_for_match(res, target, gender_threshold, tid_xor_sid)){ iter = map.erase(iter); }else{ iter++; diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index 388be6d7ea..53decf58f2 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -154,15 +154,15 @@ class AdvRngSearcher{ const std::vector& seeds, uint64_t min_advances, uint64_t max_advances, - uint16_t tid_xor_sid = 0, - uint8_t gender_threshold = 126 + int16_t gender_threshold = 126, + uint16_t tid_xor_sid = 0 ); void refine_search( std::map& map, AdvRngFilters& target, - uint16_t tid_xor_sid = 0, - uint8_t gender_threshold = 126 + int16_t gender_threshold = 126, + uint16_t tid_xor_sid = 0 ); private: @@ -171,8 +171,8 @@ class AdvRngSearcher{ AdvRngFilters& target, uint64_t min_advances, uint64_t max_advances, - uint16_t tid_xor_sid, - uint8_t gender_threshold + int16_t gender_threshold, + uint16_t tid_xor_sid ); }; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp index f51d55a761..4af5e23a6b 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -323,7 +323,10 @@ bool GiftRng::use_rare_candy( { party_menu } ); if (ret < 0){ - env.log("use_rare_candy(): failed to detect party menu."); + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "use_rare_candy(): failed to detect party menu." + ); return true; } @@ -348,7 +351,10 @@ bool GiftRng::use_rare_candy( { level_up } ); if (ret2 < 0){ - env.log("use_rare_candy(): failed to detect level-up stats."); + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "use_rare_candy(): failed to detect level-up stats." + ); return true; } @@ -367,7 +373,10 @@ bool GiftRng::use_rare_candy( int attempts = 0; while (true){ if (attempts > 5){ - env.log("use_rare_candy(): failed to return to bag menu in 5 attempts."); + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "use_rare_candy(): failed to return to bag menu in 5 attempts." + ); return true; } BagWatcher bag_menu(COLOR_RED); @@ -394,7 +403,10 @@ bool GiftRng::use_rare_candy( pbf_press_button(context, BUTTON_A, 200ms, 1800ms); continue; default: - env.log("use_rare_candy(): failed to return to bag menu."); + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "use_rare_candy(): failed to return to bag menu." + ); return true; } } @@ -428,51 +440,67 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& env.log("Target Seed Value: " + std::to_string(TARGET_SEED)); BaseStats BASE_STATS; + int16_t GENDER_THRESHOLD = -1; switch (TARGET){ case PokemonFRLG_RngTarget::magikarp: BASE_STATS = { 20, 10, 55, 15, 20, 80 }; + GENDER_THRESHOLD = 126; break; case PokemonFRLG_RngTarget::hitmonchan: BASE_STATS = { 50, 105, 79, 35, 110, 76 }; + GENDER_THRESHOLD = -1; break; case PokemonFRLG_RngTarget::hitmonlee: BASE_STATS = { 50, 120, 53, 35, 110, 87 }; + GENDER_THRESHOLD = -1; break; case PokemonFRLG_RngTarget::eevee: BASE_STATS = { 55, 55, 50, 45, 65, 55 }; + GENDER_THRESHOLD = 30; break; case PokemonFRLG_RngTarget::lapras: BASE_STATS = { 130, 85, 80, 85, 95, 60 }; + GENDER_THRESHOLD = 126; break; case PokemonFRLG_RngTarget::omanyte: BASE_STATS = { 35, 40, 100, 90, 55, 35 }; + GENDER_THRESHOLD = 30; break; case PokemonFRLG_RngTarget::kabuto: BASE_STATS = { 30, 80, 90, 55, 45, 55 }; + GENDER_THRESHOLD = 30; break; case PokemonFRLG_RngTarget::aerodactyl: BASE_STATS = { 80, 105, 65, 60, 75, 130 }; + GENDER_THRESHOLD = 30; break; case PokemonFRLG_RngTarget::gamecornerabra: BASE_STATS = { 25, 20, 15, 105, 55, 90 }; + GENDER_THRESHOLD = 63; break; case PokemonFRLG_RngTarget::gamecornerclefairy: BASE_STATS = { 70, 45, 48, 60, 65, 35 }; + GENDER_THRESHOLD = 190; break; case PokemonFRLG_RngTarget::gamecornerdratini: BASE_STATS = { 41, 64, 45, 50, 50, 50 }; + GENDER_THRESHOLD = 126; break; case PokemonFRLG_RngTarget::gamecornerscyther: BASE_STATS = { 70, 110, 80, 55, 80, 105 }; + GENDER_THRESHOLD = 126; break; case PokemonFRLG_RngTarget::gamecornerpinsir: BASE_STATS = { 65, 125, 100, 55, 70, 85 }; + GENDER_THRESHOLD = 126; break; case PokemonFRLG_RngTarget::gamecornerporygon: BASE_STATS = { 65, 60, 70, 85, 75, 40 }; + GENDER_THRESHOLD = -1; break; case PokemonFRLG_RngTarget::togepi: BASE_STATS = { 35, 20, 65, 40, 65, 20 }; + GENDER_THRESHOLD = 30; break; default: break; @@ -481,7 +509,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& const double FRAMERATE = 59.999977; // FPS const double FRAME_DURATION = 1000 / FRAMERATE; - uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 1 : 10; + uint8_t MAX_HISTORY_LENGTH = USE_TEACHY_TV ? 2 : 10; double SEED_BUMPS[] = {0, 1, -1, 2, -2}; uint64_t CONTINUE_SCREEN_FRAMES = 200; @@ -631,7 +659,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); RNG_FILTERS.set(filters); - std::map search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius); + std::map search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); POSSIBLE_HITS.set( SEED_CALIBRATION_FRAMES * FRAME_DURATION, CONTINUE_SCREEN_ADJUSTMENT, @@ -650,7 +678,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& stats.errors++; } - searcher.refine_search(search_hits, filters, 0, 30); + searcher.refine_search(search_hits, filters, GENDER_THRESHOLD); POSSIBLE_HITS.set( SEED_CALIBRATION_FRAMES * FRAME_DURATION, CONTINUE_SCREEN_ADJUSTMENT, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp index b061f3088f..8a183286b2 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp @@ -114,14 +114,17 @@ std::map get_search_results( AdvRngFilters& filters, const std::vector& SEED_VALUES, const uint64_t& ADVANCES, - const uint64_t& advances_radius + const uint64_t& advances_radius, + int16_t gender_threshold, + uint16_t tid_xor_sid + ){ std::map search_hits; for (int i=0; i<4; i++){ uint64_t adv_radius = advances_radius * (uint64_t(1) << i); uint64_t min_adv = ADVANCES - std::min(uint64_t(ADVANCES), adv_radius); uint64_t max_adv = ADVANCES + adv_radius; - search_hits = searcher.search(filters, SEED_VALUES, min_adv, max_adv, 0, 30); + search_hits = searcher.search(filters, SEED_VALUES, min_adv, max_adv, gender_threshold, tid_xor_sid); if (search_hits.size() > 0){ console.log("Number of search hits: " + std::to_string(search_hits.size())); return search_hits; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h index 484ad8d151..51558b40e8 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.h @@ -46,7 +46,9 @@ std::map get_search_results( AdvRngFilters& filters, const std::vector& SEED_VALUES, const uint64_t& ADVANCES, - const uint64_t& advances_radius + const uint64_t& advances_radius, + int16_t gender_threshold = 126, + uint16_t tid_xor_sid = 0 ); // update IV ranges for RNG search filters with new stats/EVs after leveling up diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index 42531db614..68cb0fb87c 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -320,7 +320,14 @@ bool StarterRng::walk_to_rival_battle(SingleSwitchProgramEnvironment& env, ProCo { black_screen } ); - return (ret < 0); + bool failed = ret < 0; + if (failed){ + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "walk_to_rival_battle(): failed to initiate battle." + ); + } + return failed; } bool StarterRng::auto_battle_rival( @@ -353,7 +360,10 @@ bool StarterRng::auto_battle_rival( { battle_ready } ); if (ret1 < 0){ - env.log("auto_battle_rival(): failed to detect the battle menu."); + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "auto_battle_rival(): failed to detect the battle menu." + ); return true; } env.log("Battle started. Using first move..."); @@ -370,7 +380,10 @@ bool StarterRng::auto_battle_rival( { battle_ready } ); if (ret2 < 0){ - env.log("auto_battle_rival(): failed to detect the battle menu."); + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "auto_battle_rival(): failed to detect the battle menu." + ); return true; } env.log("Oak tutorial dialogue finished. Mashing A..."); @@ -396,7 +409,10 @@ bool StarterRng::auto_battle_rival( pbf_mash_button(context, BUTTON_B, 20s); // exit battle and dialogue return false; default: - env.log("auto_battle_rival(): no fainting detected with 5 minutes."); + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "auto_battle_rival(): no fainting detected with 5 minutes." + ); return true; } @@ -461,7 +477,10 @@ bool StarterRng::walk_to_route1_from_lab(SingleSwitchProgramEnvironment& env, Pr ); if (ret < 0){ - env.log("walk_to_route1_from_lab(): failed to exit lab."); + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "walk_to_route1_from_lab(): failed to exit lab." + ); return true; } @@ -491,7 +510,10 @@ bool StarterRng::walk_to_route1_from_home(SingleSwitchProgramEnvironment& env, P ); if (ret < 0){ - env.log("walk_to_route1_from_home(): failed to exit the house."); + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "walk_to_route1_from_home(): failed to exit the house." + ); return true; } @@ -524,7 +546,10 @@ int StarterRng::autolevel_on_route1( env.log("Triggering wild encounters..."); int ret = grass_spin(env.console, context, leftright); if (ret < 0){ - env.log("autolevel_on_route1(): failed to trigger encounter."); + send_program_recoverable_error_notification( + env, NOTIFICATION_ERROR_RECOVERABLE, + "autolevel_on_route1(): failed to trigger encounter." + ); return -1; } @@ -791,7 +816,6 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte bool failed = walk_to_rival_battle(env, context); if (failed){ stats.errors++; - env.log("Failed to initiate rival battle."); continue; // reset game } @@ -801,7 +825,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte continue; // reset game } if (pokemon.level.size() > 1){ - searcher.refine_search(search_hits, filters, 0, 30); + searcher.refine_search(search_hits, filters, 30); POSSIBLE_HITS.set(SEED_CALIBRATION_FRAMES, CONTINUE_SCREEN_ADJUSTMENT, ADVANCES_CALIBRATION, search_hits); env.log("Number of search hits: " + std::to_string(search_hits.size())); finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 5); @@ -831,7 +855,6 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte int ret2 = autolevel_on_route1(env, context, pokemon, filters, BASE_STATS); if (ret2 < 0){ - env.log("Error encountered while auto-leveling."); stats.errors++; break; }else if(ret2 == 1){ @@ -857,7 +880,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte if (pokemon.level.size() > num_levels){ num_levels = pokemon.level.size(); - searcher.refine_search(search_hits, filters, 0, 30); + searcher.refine_search(search_hits, filters, 30); POSSIBLE_HITS.set( SEED_CALIBRATION_FRAMES * FRAME_DURATION, CONTINUE_SCREEN_ADJUSTMENT, From f1054f5e928d2556668a0a8f2c78d798332ba47a Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 26 Apr 2026 18:08:57 -0500 Subject: [PATCH 03/12] calibration and other fixes --- .../Source/Pokemon/Pokemon_AdvRng.h | 3 + .../RngManipulation/PokemonFRLG_GiftRng.cpp | 24 +++---- .../RngManipulation/PokemonFRLG_GiftRng.h | 2 +- .../PokemonFRLG_RngCalibration.cpp | 62 ++++++++++++++++++- .../PokemonFRLG_RngDisplays.cpp | 18 +++--- .../RngManipulation/PokemonFRLG_RngDisplays.h | 7 ++- .../RngManipulation/PokemonFRLG_SidHelper.cpp | 5 +- .../PokemonFRLG_StarterRng.cpp | 34 +++++----- .../RngManipulation/PokemonFRLG_StarterRng.h | 2 +- 9 files changed, 111 insertions(+), 46 deletions(-) diff --git a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h index 53decf58f2..169b08163d 100644 --- a/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h +++ b/SerialPrograms/Source/Pokemon/Pokemon_AdvRng.h @@ -132,6 +132,9 @@ struct AdvRngFilters{ // input EVs are the ones earned since the last level up, not the total void level_up_observed_pokemon(AdvObservedPokemon& pokemon, StatReads& newstats, EVs& evyield); +// returns the appropriate NatureAdjustments for an AdvNature +Pokemon::NatureAdjustments nature_to_adjustment(AdvNature nature); + // returns search filters that correspond with observed stats AdvRngFilters observation_to_filters(const AdvObservedPokemon& observation, const BaseStats& basestats, AdvRngMethod method = AdvRngMethod::Method1); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp index 4af5e23a6b..6ed113b5a3 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -201,7 +201,7 @@ GiftRng::GiftRng() }) { PA_ADD_OPTION(RNG_FILTERS); - PA_ADD_OPTION(POSSIBLE_HITS); + PA_ADD_OPTION(RNG_CALIBRATION); PA_ADD_OPTION(LANGUAGE); PA_ADD_OPTION(TARGET); PA_ADD_OPTION(MAX_RESETS); @@ -423,7 +423,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& home_black_border_check(env.console, context); RNG_FILTERS.reset(); - POSSIBLE_HITS.reset(); + RNG_CALIBRATION.reset(); const uint16_t TARGET_SEED = parse_seed(env.console, SEED); const std::vector SEED_VALUES = parse_seed_list(env.console, SEED_LIST); @@ -515,9 +515,9 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& uint64_t CONTINUE_SCREEN_FRAMES = 200; const int64_t FIXED_SEED_OFFSET = -845; // milliseconds. approximate; - double SEED_CALIBRATION_FRAMES = 0; - double ADVANCES_CALIBRATION = 0; - double CONTINUE_SCREEN_ADJUSTMENT = 0; + double SEED_CALIBRATION_FRAMES = RNG_CALIBRATION.seed_calibration / FRAME_DURATION; + double ADVANCES_CALIBRATION = RNG_CALIBRATION.advances_calibration; + double CONTINUE_SCREEN_ADJUSTMENT = RNG_CALIBRATION.csf_calibration; AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); AdvPokemonResult target_result = searcher.generate_pokemon(); @@ -571,8 +571,10 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& } env.log("Advances search radius: " + std::to_string(advances_radius)); - SEED_CALIBRATION_FRAMES = get_seed_calibration_frames(CALIBRATION_HISTORY, SEED_VALUES, SEED_POSITION); - ADVANCES_CALIBRATION = get_advances_calibration_frames(CALIBRATION_HISTORY, ADVANCES); + if (CALIBRATION_HISTORY.results.size() > 0){ + SEED_CALIBRATION_FRAMES = get_seed_calibration_frames(CALIBRATION_HISTORY, SEED_VALUES, SEED_POSITION); + ADVANCES_CALIBRATION = get_advances_calibration_frames(CALIBRATION_HISTORY, ADVANCES); + } if (CALIBRATION_HISTORY.results.size() > 0){ AdvRngState prev_hit = CALIBRATION_HISTORY.results.back(); @@ -633,7 +635,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& stats.resets++; RNG_FILTERS.reset(); - POSSIBLE_HITS.reset(); + RNG_CALIBRATION.reset(); bool shiny_found = check_for_shiny(env.console, context, TARGET); @@ -660,7 +662,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& RNG_FILTERS.set(filters); std::map search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); - POSSIBLE_HITS.set( + RNG_CALIBRATION.set( SEED_CALIBRATION_FRAMES * FRAME_DURATION, CONTINUE_SCREEN_ADJUSTMENT, ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, @@ -678,8 +680,8 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& stats.errors++; } - searcher.refine_search(search_hits, filters, GENDER_THRESHOLD); - POSSIBLE_HITS.set( + search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); + RNG_CALIBRATION.set( SEED_CALIBRATION_FRAMES * FRAME_DURATION, CONTINUE_SCREEN_ADJUSTMENT, ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h index bbfb8c96f8..78b7050959 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h @@ -69,7 +69,7 @@ class GiftRng : public SingleSwitchProgramInstance{ SimpleIntegerOption MAX_RARE_CANDIES; RngFilterDisplay RNG_FILTERS; - RngCalibrationDisplay POSSIBLE_HITS; + RngCalibrationDisplay RNG_CALIBRATION; StringOption SEED; TextEditOption SEED_LIST; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp index 8a183286b2..5b4c731393 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngCalibration.cpp @@ -134,6 +134,25 @@ std::map get_search_results( return search_hits; } +bool range_is_valid(IvRange iv){ + return ( + iv.low <= iv.high && + iv.low >= 0 && + iv.high >= 0 + ); +} + +bool ranges_are_valid(IvRanges ivs){ + return ( + range_is_valid(ivs.hp) && + range_is_valid(ivs.attack) && + range_is_valid(ivs.defense) && + range_is_valid(ivs.spatk) && + range_is_valid(ivs.spdef) && + range_is_valid(ivs.speed) + ); +} + void update_filters( AdvRngFilters& filters, AdvObservedPokemon& pokemon, @@ -141,12 +160,49 @@ void update_filters( const EVs& evyield, const BaseStats& BASE_STATS ){ + EVs old_evs = pokemon.evs.back(); + EVs new_evs = { + old_evs.hp + evyield.hp, + old_evs.attack + evyield.attack, + old_evs.defense + evyield.defense, + old_evs.spatk + evyield.spatk, + old_evs.spdef + evyield.spdef, + old_evs.speed + evyield.speed + }; + pokemon.level.emplace_back(pokemon.level.back() + 1); pokemon.stats.emplace_back(stats); - pokemon.evs.emplace_back(evyield); + pokemon.evs.emplace_back(new_evs); + + while (true){ + // in the worst case (the new stats are the problem), start over + if (pokemon.level.size() == 0){ + filters.ivs = { {0,31}, {0,31}, {0,31}, {0,31}, {0,31}, {0,31} }; + return; + } + + AdvRngFilters new_filters = observation_to_filters(pokemon, BASE_STATS); + + if (!ranges_are_valid(new_filters.ivs)){ + IvRanges new_stat_ivs = calc_iv_ranges(BASE_STATS, pokemon.level.back(), pokemon.evs.back(), pokemon.stats.back(), nature_to_adjustment(pokemon.nature)); + if (!ranges_are_valid(new_stat_ivs)){ + // remove newest stats first if they aren't valid + pokemon.level.erase(pokemon.level.begin()); + pokemon.stats.erase(pokemon.stats.begin()); + pokemon.evs.erase(pokemon.evs.begin()); + }else{ + // remove oldest stats first + pokemon.level.pop_back(); + pokemon.stats.pop_back(); + pokemon.evs.pop_back(); + } + continue; + } + + filters.ivs = new_filters.ivs; + return; + } - AdvRngFilters new_filters = observation_to_filters(pokemon, BASE_STATS); - filters.ivs = new_filters.ivs; } double get_seed_calibration_frames( diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp index 79ee04b6ee..dc747105b0 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp @@ -157,9 +157,9 @@ void RngFilterDisplay::reset(){ RngCalibrationDisplay::RngCalibrationDisplay() : GroupOption("RNG Calibration", LockMode::READ_ONLY) - , seed_calibration(false, "Seed Calibration (ms):", LockMode::READ_ONLY, "-", "") - , csf_calibration(false, "Continue Screen Frames Calibration:", LockMode::READ_ONLY, "-", "") - , advances_calibration(false, "In-Game Advances Calibration:", LockMode::READ_ONLY, "-", "") + , seed_calibration("Seed Calibration (ms):", LockMode::LOCK_WHILE_RUNNING, 0.0) + , csf_calibration("Continue Screen Frames Calibration:", LockMode::LOCK_WHILE_RUNNING, 0.0) + , advances_calibration("In-Game Advances Calibration:", LockMode::LOCK_WHILE_RUNNING, 0.0) , hits(false, "Seeds/Advances:", LockMode::READ_ONLY, "-", "") { PA_ADD_STATIC(seed_calibration); @@ -205,9 +205,9 @@ void RngCalibrationDisplay::set( double a_calibration, std::vector& rng_states ){ - seed_calibration.set(std::to_string(s_calibration)); - csf_calibration.set(std::to_string(c_calibration)); - advances_calibration.set(std::to_string(a_calibration)); + seed_calibration.set(s_calibration); + csf_calibration.set(c_calibration); + advances_calibration.set(a_calibration); hits.set(get_hits_string(rng_states)); } void RngCalibrationDisplay::set( @@ -221,9 +221,9 @@ void RngCalibrationDisplay::set( } void RngCalibrationDisplay::reset(){ - seed_calibration.set("-"); - csf_calibration.set("-"); - advances_calibration.set("-"); + // seed_calibration.set(0.0); + // csf_calibration.set(0.0); + // advances_calibration.set(0.0); hits.set("-"); } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h index ada567a522..804dbd027d 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h @@ -10,6 +10,7 @@ #include #include "Common/Cpp/Options/StringOption.h" #include "Common/Cpp/Options/TextEditOption.h" +#include "Common/Cpp/Options/FloatingPointOption.h" #include "CommonFramework/Notifications/EventNotificationsTable.h" #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" #include "Pokemon/Pokemon_AdvRng.h" @@ -82,9 +83,9 @@ class RngCalibrationDisplay : public GroupOption{ static std::string get_hits_string(const std::vector& rng_states); static std::string get_hits_string(const std::map& hits_map); public: - StringOption seed_calibration; - StringOption csf_calibration; - StringOption advances_calibration; + FloatingPointOption seed_calibration; + FloatingPointOption csf_calibration; + FloatingPointOption advances_calibration; StringOption hits; }; diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp index 8e7add03b7..b48f164fb7 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_SidHelper.cpp @@ -151,9 +151,8 @@ void finish_intro_animations(SingleSwitchProgramEnvironment& env, ProControllerC ); if (ret2 < 0){ - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, "SidHelper(): black screen lasted longer than 10 seconds", env.console - ); + env.log("finish_intro_animations(): end of black screen not detected within 10 seconds. Continuing anyway..."); + // if there's a problem, an error will be thrown when trying to navigate to the trainer card } pbf_wait(context, 2000ms); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index 68cb0fb87c..3a5e14ea06 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -186,7 +186,7 @@ StarterRng::StarterRng() }) { PA_ADD_OPTION(RNG_FILTERS); - PA_ADD_OPTION(POSSIBLE_HITS); + PA_ADD_OPTION(RNG_CALIBRATION); PA_ADD_OPTION(LANGUAGE); PA_ADD_OPTION(STARTER); PA_ADD_OPTION(MAX_RESETS); @@ -628,7 +628,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte home_black_border_check(env.console, context); RNG_FILTERS.reset(); - POSSIBLE_HITS.reset(); + RNG_CALIBRATION.reset(); const uint16_t TARGET_SEED = parse_seed(env.console, SEED); const std::vector SEED_VALUES = parse_seed_list(env.console, SEED_LIST); @@ -659,6 +659,8 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte break; } + const int16_t GENDER_THRESHOLD = 30; + const double FRAMERATE = 59.999977; // FPS const double FRAME_DURATION = 1000 / FRAMERATE; @@ -668,9 +670,9 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte uint64_t CONTINUE_SCREEN_FRAMES = 200; const int64_t FIXED_SEED_OFFSET = -845; // milliseconds. approximate; - double SEED_CALIBRATION_FRAMES = 0; - double ADVANCES_CALIBRATION = 0; - double CONTINUE_SCREEN_ADJUSTMENT = 0; + double SEED_CALIBRATION_FRAMES = RNG_CALIBRATION.seed_calibration / FRAME_DURATION; + double ADVANCES_CALIBRATION = RNG_CALIBRATION.advances_calibration; + double CONTINUE_SCREEN_ADJUSTMENT = RNG_CALIBRATION.csf_calibration; AdvRngSearcher searcher(TARGET_SEED, ADVANCES, AdvRngMethod::Method1); AdvPokemonResult target_result = searcher.generate_pokemon(); @@ -724,9 +726,11 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte } env.log("Advances search radius: " + std::to_string(advances_radius)); - SEED_CALIBRATION_FRAMES = get_seed_calibration_frames(CALIBRATION_HISTORY, SEED_VALUES, SEED_POSITION); - ADVANCES_CALIBRATION = get_advances_calibration_frames(CALIBRATION_HISTORY, ADVANCES); - + if (CALIBRATION_HISTORY.results.size() > 0){ + SEED_CALIBRATION_FRAMES = get_seed_calibration_frames(CALIBRATION_HISTORY, SEED_VALUES, SEED_POSITION); + ADVANCES_CALIBRATION = get_advances_calibration_frames(CALIBRATION_HISTORY, ADVANCES); + } + if (CALIBRATION_HISTORY.results.size() > 0){ AdvRngState prev_hit = CALIBRATION_HISTORY.results.back(); double prev_csf_calibration = CALIBRATION_HISTORY.continue_screen_adjustments.back(); @@ -774,7 +778,7 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte stats.resets++; RNG_FILTERS.reset(); - POSSIBLE_HITS.reset(); + RNG_CALIBRATION.reset(); bool shiny_found = check_for_shiny(env.console, context, PokemonFRLG_RngTarget::starters); @@ -800,8 +804,8 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte AdvRngFilters filters = observation_to_filters(pokemon, BASE_STATS); RNG_FILTERS.set(filters); - std::map search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius); - POSSIBLE_HITS.set( + std::map search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); + RNG_CALIBRATION.set( SEED_CALIBRATION_FRAMES * FRAME_DURATION, CONTINUE_SCREEN_ADJUSTMENT, ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, @@ -825,8 +829,8 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte continue; // reset game } if (pokemon.level.size() > 1){ - searcher.refine_search(search_hits, filters, 30); - POSSIBLE_HITS.set(SEED_CALIBRATION_FRAMES, CONTINUE_SCREEN_ADJUSTMENT, ADVANCES_CALIBRATION, search_hits); + search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); + RNG_CALIBRATION.set(SEED_CALIBRATION_FRAMES, CONTINUE_SCREEN_ADJUSTMENT, ADVANCES_CALIBRATION, search_hits); env.log("Number of search hits: " + std::to_string(search_hits.size())); finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 5); if (finished){ @@ -880,8 +884,8 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte if (pokemon.level.size() > num_levels){ num_levels = pokemon.level.size(); - searcher.refine_search(search_hits, filters, 30); - POSSIBLE_HITS.set( + search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); + RNG_CALIBRATION.set( SEED_CALIBRATION_FRAMES * FRAME_DURATION, CONTINUE_SCREEN_ADJUSTMENT, ADVANCES_CALIBRATION - CONTINUE_SCREEN_ADJUSTMENT, diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h index 1ffe034021..470e9d1270 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.h @@ -78,7 +78,7 @@ class StarterRng : public SingleSwitchProgramInstance{ SimpleIntegerOption MAX_RESETS; RngFilterDisplay RNG_FILTERS; - RngCalibrationDisplay POSSIBLE_HITS; + RngCalibrationDisplay RNG_CALIBRATION; StringOption SEED; TextEditOption SEED_LIST; From 6a28778858c9b0e8b0ab513ffa9fd88924d9dcba Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 26 Apr 2026 22:18:14 -0500 Subject: [PATCH 04/12] more small fixes --- .../Programs/RngManipulation/PokemonFRLG_HardReset.cpp | 3 ++- .../Programs/RngManipulation/PokemonFRLG_StarterRng.cpp | 7 ++++++- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp index 0751aebdea..b91a56d53a 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp @@ -101,7 +101,8 @@ void rng_start_game_from_home( check_online, failed_to_connect, black_screen, - } + }, + 1ms ); // Wait for screen to stabilize. diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp index 3a5e14ea06..a57a9b907f 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_StarterRng.cpp @@ -830,7 +830,12 @@ void StarterRng::program(SingleSwitchProgramEnvironment& env, ProControllerConte } if (pokemon.level.size() > 1){ search_hits = get_search_results(env.console, searcher, filters, SEED_VALUES, ADVANCES, advances_radius, GENDER_THRESHOLD); - RNG_CALIBRATION.set(SEED_CALIBRATION_FRAMES, CONTINUE_SCREEN_ADJUSTMENT, ADVANCES_CALIBRATION, search_hits); + RNG_CALIBRATION.set( + SEED_CALIBRATION_FRAMES * FRAME_DURATION, + CONTINUE_SCREEN_ADJUSTMENT, + ADVANCES_CALIBRATION, + search_hits + ); env.log("Number of search hits: " + std::to_string(search_hits.size())); finished = update_history(env.console, ADVANCE_HISTORY, CALIBRATION_HISTORY, MAX_HISTORY_LENGTH, SEED_CALIBRATION_FRAMES, ADVANCES_CALIBRATION, CONTINUE_SCREEN_ADJUSTMENT, search_hits, 5); if (finished){ From bd62df0380707f84c5fce8a91197ddb30fedf97f Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 26 Apr 2026 22:36:26 -0500 Subject: [PATCH 05/12] even more tweaks to resets --- .../Programs/RngManipulation/PokemonFRLG_HardReset.cpp | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp index b91a56d53a..eca8053668 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp @@ -78,7 +78,8 @@ void rng_start_game_from_home( int ret1 = wait_until( console, context, std::chrono::seconds(2), - { black_screen } + { black_screen }, + 1ms ); switch (ret1){ @@ -125,7 +126,7 @@ void rng_start_game_from_home( break; case 3: console.log("Detected check online.", COLOR_BLUE); - context.wait_for(std::chrono::seconds(1)); + context.wait_for(std::chrono::milliseconds(100)); break; case 4: console.log("Detected failed to connect.", COLOR_BLUE); @@ -135,7 +136,7 @@ void rng_start_game_from_home( console.log("Detected black screen. Game started..."); return; default: - console.log("start_game_from_home_with_inference(): No recognizable state after 30 seconds.", COLOR_RED); + console.log("rng_start_game_from_home(): No recognizable state after 30 seconds.", COLOR_RED); pbf_press_button(context, BUTTON_HOME, 160ms, 840ms); } } From c92200b24cb8da934cd96b04c5078603152933d1 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Sun, 26 Apr 2026 23:18:35 -0500 Subject: [PATCH 06/12] fix issue with signedness --- .../Programs/RngManipulation/PokemonFRLG_GiftRng.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp index 6ed113b5a3..9462f93595 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -674,7 +674,7 @@ void GiftRng::program(SingleSwitchProgramEnvironment& env, ProControllerContext& continue; } - for (int i=0; i Date: Mon, 27 Apr 2026 15:21:16 -0500 Subject: [PATCH 07/12] rework rng resets --- .../PokemonFRLG_BlindNavigation.cpp | 4 +- .../RngManipulation/PokemonFRLG_HardReset.cpp | 205 +++++++++++------- 2 files changed, 125 insertions(+), 84 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp index a37061114b..1c44a297db 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_BlindNavigation.cpp @@ -62,9 +62,9 @@ void set_seed_after_delay(ProControllerContext& context, SeedButton SEED_BUTTON, void load_game_after_delay(ProControllerContext& context, uint64_t CONTINUE_SCREEN_DELAY){ pbf_wait(context, std::chrono::milliseconds(CONTINUE_SCREEN_DELAY - 3000)); - pbf_press_button(context, BUTTON_A, 33ms, 1467ms); + pbf_press_button(context, BUTTON_A, 50ms, 1450ms); // skip recap - pbf_press_button(context, BUTTON_B, 33ms, 2467ms); + pbf_press_button(context, BUTTON_B, 50ms, 2450ms); // need to later subtract 4000ms from delay to hit desired number of advances } diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp index eca8053668..0adbb71fb4 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_HardReset.cpp @@ -27,125 +27,172 @@ namespace PokemonAutomation{ namespace NintendoSwitch{ namespace PokemonFRLG{ -void rng_start_game_from_home( +void rng_reset_and_return_home( ConsoleHandle& console, ProControllerContext& context, - uint8_t game_slot, uint8_t user_slot ){ - context.wait_for_all_requests(); - { - HomeMenuWatcher detector(console); - int ret = run_until( - console, context, - [](ProControllerContext& context){ - pbf_mash_button(context, BUTTON_B, 10000ms); - }, - { detector } - ); - if (ret == 0){ - console.log("Detected Home screen."); - }else{ + // close the game + go_home(console, context); + close_game_from_home(console, context); + + // console specific delays between opening the game and returning to the Home screen + ConsoleType console_type = console.state().console_type(); // go_home() has already been called, so this should already be detected + Milliseconds launch_delay = (console_type == ConsoleType::Switch1) ? 450ms : 950ms; + + bool extra_resume_press = false; + WallClock deadline = current_time() + std::chrono::minutes(5); + while (true){ + if (current_time() > deadline){ OperationFailedException::fire( ErrorReport::SEND_ERROR_REPORT, - "start_game_from_home_with_inference(): Failed to detect Home screen after 10 seconds.", + "rng_start_game_and_return_home(): Failed to start game and return to Home within 5 minutes.", console ); } - context.wait_for(std::chrono::milliseconds(100)); - } - if (game_slot != 0){ - ssf_press_button(context, BUTTON_HOME, ConsoleSettings::instance().SETTINGS_TO_HOME_DELAY0, 160ms); - for (uint8_t c = 1; c < game_slot; c++){ - ssf_press_dpad_ptv(context, DPAD_RIGHT, 160ms); - } - context.wait_for_all_requests(); - } - - pbf_press_button(context, BUTTON_A, 160ms, 340ms); - - WallClock deadline = current_time() + std::chrono::minutes(5); - while (current_time() < deadline){ - HomeMenuWatcher home(console, std::chrono::milliseconds(2000)); StartGameUserSelectWatcher user_select(console, COLOR_GREEN); + HomeMenuWatcher home(console, std::chrono::milliseconds(2000)); UpdateMenuWatcher update_menu(console, COLOR_PURPLE); CheckOnlineWatcher check_online(COLOR_CYAN); FailedToConnectWatcher failed_to_connect(COLOR_YELLOW); - BlackScreenWatcher black_screen(COLOR_BLUE, {0.1, 0.15, 0.8, 0.7}); - // spend a little bit longer waiting for the black screen to avoid missing it - context.wait_for_all_requests(); - int ret1 = wait_until( - console, context, - std::chrono::seconds(2), - { black_screen }, - 1ms - ); - - switch (ret1){ - case 0: - console.log("Detected black screen. Game started..."); - return; - default: - console.log("Black screen not detected. Checking for other states..."); - } - - // handle other states + // first, get to the user select screen context.wait_for_all_requests(); int ret2 = wait_until( console, context, std::chrono::seconds(30), { - home, user_select, + home, update_menu, check_online, failed_to_connect, - black_screen, - }, - 1ms + } ); // Wait for screen to stabilize. context.wait_for(std::chrono::milliseconds(100)); + // some of these states might not be relevant at this point (if they would only show up after pressing A on a profile) switch (ret2){ case 0: - console.log("Detected home screen (again).", COLOR_BLUE); - pbf_press_button(context, BUTTON_A, 160ms, 840ms); - break; - case 1: console.log("Detected user-select screen."); move_to_user(context, user_slot); - pbf_press_button(context, BUTTON_A, 160ms, 320ms); break; + case 1: + console.log("Detected Home screen.", COLOR_BLUE); + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + continue; case 2: console.log("Detected update menu.", COLOR_BLUE); pbf_press_dpad(context, DPAD_UP, 40ms, 0ms); - pbf_press_button(context, BUTTON_A, 160ms, 840ms); - break; + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + continue; case 3: console.log("Detected check online.", COLOR_BLUE); - context.wait_for(std::chrono::milliseconds(100)); - break; + context.wait_for(std::chrono::seconds(1)); + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + continue; case 4: console.log("Detected failed to connect.", COLOR_BLUE); pbf_press_button(context, BUTTON_A, 160ms, 840ms); - break; - case 5: - console.log("Detected black screen. Game started..."); - return; + continue; default: - console.log("rng_start_game_from_home(): No recognizable state after 30 seconds.", COLOR_RED); + console.log("rng_start_game_and_return_home(): No recognizable state after 30 seconds.", COLOR_RED); pbf_press_button(context, BUTTON_HOME, 160ms, 840ms); + continue; } - } - OperationFailedException::fire( - ErrorReport::SEND_ERROR_REPORT, - "rng_start_game_from_home(): Failed to start game after 5 minutes.", - console - ); + context.wait_for_all_requests(); + + // By this point, the user selection menu is open with the desired profile selected, and the game hasn't yet been started. + // Everything up to this point has not been time-sensitive. + // Waiting for all requests and using inference should be avoided for any button presses that happen *while the game is open*, + // but we can make sure we've gotten back to the home screen and pause there + + // open the game and go back home ASAP + if (extra_resume_press){ + pbf_press_button(context, BUTTON_A, 160ms, 840ms); + } + pbf_press_button(context, BUTTON_A, 50ms, launch_delay); + pbf_press_button(context, BUTTON_HOME, 200ms, 1800ms); + + while(current_time() < deadline){ + // make sure a black screen appeared as a result of the button presses + // if a popup appears, flag that it happened even if it gets closed by the home button press + // Not sure how to handle the online check, so leaving it out for now + BlackScreenWatcher black_screen(COLOR_BLUE, {0.1, 0.15, 0.8, 0.7}); + int ret3 = wait_until( + console, context, + std::chrono::seconds(2), + { black_screen, update_menu, failed_to_connect }, + 1ms + ); + bool black_screen_detected = false; + switch (ret3){ + case 0: + console.log("Black screen detected."); + black_screen_detected = true; + break; + case 1: + console.log("Update menu detected.", COLOR_BLUE); + extra_resume_press = true; + case 2: + console.log("Failed to connect detected.", COLOR_BLUE); + extra_resume_press = true; + } + + context.wait_for_all_requests(); + + // make sure we're back at the home screen. Otherwise, handle any undesired states + int ret4 = wait_until( + console, context, + std::chrono::seconds(10), + { + home, + user_select, + update_menu, + failed_to_connect + } + ); + + switch (ret4){ + case 0: + if (black_screen_detected){ + console.log("Detected Home screen after black screen."); + return; + }else{ + console.log("Detected Home screen, but no black screen. Trying again from the beginning...", COLOR_BLUE); + launch_delay += 250ms; // bump this up in case it was too short + break; // back to the outer loop + } + return; + case 1: + console.log("Detected user-select screen. Trying again...", COLOR_BLUE); + move_to_user(context, user_slot); + pbf_press_button(context, BUTTON_A, 50ms, launch_delay); + pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); + continue; + case 2: + console.log("Detected update menu. Trying again...", COLOR_BLUE); + pbf_press_dpad(context, DPAD_UP, 40ms, 0ms); + pbf_press_button(context, BUTTON_A, 50ms, launch_delay); + pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); + continue; + case 3: + console.log("Detected failed to connect.", COLOR_BLUE); + pbf_press_button(context, BUTTON_A, 50ms, launch_delay); + pbf_press_button(context, BUTTON_HOME, 200ms, 2800ms); + continue; + default: + console.log("rng_start_game_and_return_home(): No recognizable state after 10 seconds.", COLOR_RED); + pbf_press_button(context, BUTTON_HOME, 160ms, 840ms); + break; // back to outer loop + } + + break; + } + } } @@ -162,13 +209,7 @@ void reset_and_perform_blind_sequence( bool SAFARI_ZONE, uint8_t PROFILE ){ - // close the game - go_home(console, context); - close_game_from_home(console, context); - // start the game and quickly go back home - rng_start_game_from_home(console, context, uint8_t(0), PROFILE); - pbf_wait(context, 200ms); // wait a moment to ensure the game doesn't fail to launch - go_home(console, context); + rng_reset_and_return_home(console, context, PROFILE); // attempt to resume the game and perform the blind sequence // by this point, the license check should be over, so we don't need to worry about it when resuming the game @@ -219,7 +260,7 @@ void reset_and_perform_blind_sequence( void reset_and_detect_copyright_text(ConsoleHandle& console, ProControllerContext& context, uint8_t PROFILE){ go_home(console, context); close_game_from_home(console, context); - rng_start_game_from_home(console, context, uint8_t(0), PROFILE); + rng_start_game_and_return_home(console, context, uint8_t(0), PROFILE); pbf_wait(context, 200ms); // add an extra delay to try to ensure the game doesn't fail to launch go_home(console, context); From bec0df8407806f7f7d41ccd101161dc22d2cf522 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 27 Apr 2026 17:18:09 -0500 Subject: [PATCH 08/12] make displayed seed calibrations ints --- .../Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp | 4 ++-- .../Programs/RngManipulation/PokemonFRLG_RngDisplays.h | 3 ++- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp index dc747105b0..749857e618 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp @@ -157,7 +157,7 @@ void RngFilterDisplay::reset(){ RngCalibrationDisplay::RngCalibrationDisplay() : GroupOption("RNG Calibration", LockMode::READ_ONLY) - , seed_calibration("Seed Calibration (ms):", LockMode::LOCK_WHILE_RUNNING, 0.0) + , seed_calibration("Seed Calibration (ms):", LockMode::LOCK_WHILE_RUNNING, 0) , csf_calibration("Continue Screen Frames Calibration:", LockMode::LOCK_WHILE_RUNNING, 0.0) , advances_calibration("In-Game Advances Calibration:", LockMode::LOCK_WHILE_RUNNING, 0.0) , hits(false, "Seeds/Advances:", LockMode::READ_ONLY, "-", "") @@ -205,7 +205,7 @@ void RngCalibrationDisplay::set( double a_calibration, std::vector& rng_states ){ - seed_calibration.set(s_calibration); + seed_calibration.set(int64_t(std::round(s_calibration))); csf_calibration.set(c_calibration); advances_calibration.set(a_calibration); hits.set(get_hits_string(rng_states)); diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h index 804dbd027d..5bd3668f04 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.h @@ -10,6 +10,7 @@ #include #include "Common/Cpp/Options/StringOption.h" #include "Common/Cpp/Options/TextEditOption.h" +#include "Common/Cpp/Options/SimpleIntegerOption.h" #include "Common/Cpp/Options/FloatingPointOption.h" #include "CommonFramework/Notifications/EventNotificationsTable.h" #include "NintendoSwitch/NintendoSwitch_SingleSwitchProgram.h" @@ -83,7 +84,7 @@ class RngCalibrationDisplay : public GroupOption{ static std::string get_hits_string(const std::vector& rng_states); static std::string get_hits_string(const std::map& hits_map); public: - FloatingPointOption seed_calibration; + SimpleIntegerOption seed_calibration; FloatingPointOption csf_calibration; FloatingPointOption advances_calibration; StringOption hits; From 4459e846864486ff1de79cdb81a56b491b15df0e Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 27 Apr 2026 18:11:33 -0500 Subject: [PATCH 09/12] include cmath --- .../Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp index 749857e618..f1588bdd07 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngDisplays.cpp @@ -4,6 +4,7 @@ * */ +#include #include #include #include From 1e393e4cc37334e51579e3abf17e3e2fdbdc9799 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 27 Apr 2026 21:23:22 -0500 Subject: [PATCH 10/12] remove copy/paste mistake --- .../Programs/RngManipulation/PokemonFRLG_GiftRng.h | 5 ----- 1 file changed, 5 deletions(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h index 78b7050959..33682b42d3 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.h @@ -42,11 +42,6 @@ class GiftRng : public SingleSwitchProgramInstance{ ) override{} private: - enum class Gift{ - bulbasaur, - squirtle, - charmander - }; bool have_hit_target(SingleSwitchProgramEnvironment& env, const uint32_t& TARGET_SEED, const AdvRngState& hit); From 16a532e637ed84852d458792cb2dfd6d21e92e54 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 27 Apr 2026 21:35:58 -0500 Subject: [PATCH 11/12] another copy/paste fix --- .../Programs/RngManipulation/PokemonFRLG_GiftRng.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp index 9462f93595..cbe8c4aa64 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_GiftRng.cpp @@ -41,7 +41,7 @@ GiftRng_Descriptor::GiftRng_Descriptor() "PokemonFRLG:GiftRng", Pokemon::STRING_POKEMON + " FRLG", "Gift RNG", "Programs/PokemonFRLG/GiftRng.html", - "Automatically calibrate timings to hit a specific RNG target for FRLG starters.", + "Automatically calibrate timings to hit a specific RNG target for FRLG gift " + STRING_POKEMON, ProgramControllerClass::StandardController_RequiresPrecision, FeedbackType::REQUIRED, AllowCommandsWhenRunning::DISABLE_COMMANDS From 0742f83f8f0b5074a7068b8dfcf5d42a1de8bd08 Mon Sep 17 00:00:00 2001 From: theAstrogoth Date: Mon, 27 Apr 2026 21:46:48 -0500 Subject: [PATCH 12/12] add missing targets to check_for_shiny --- .../Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp index c8f02c5724..6111ba371e 100644 --- a/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp +++ b/SerialPrograms/Source/PokemonFRLG/Programs/RngManipulation/PokemonFRLG_RngNavigation.cpp @@ -145,13 +145,20 @@ bool check_for_shiny(ConsoleHandle& console, ProControllerContext& context, Poke case PokemonFRLG_RngTarget::togepi: hatch_togepi_egg(console, context); case PokemonFRLG_RngTarget::magikarp: + case PokemonFRLG_RngTarget::hitmonlee: + case PokemonFRLG_RngTarget::hitmonchan: case PokemonFRLG_RngTarget::hitmon: case PokemonFRLG_RngTarget::eevee: case PokemonFRLG_RngTarget::lapras: + case PokemonFRLG_RngTarget::omanyte: + case PokemonFRLG_RngTarget::kabuto: + case PokemonFRLG_RngTarget::aerodactyl: case PokemonFRLG_RngTarget::fossils: case PokemonFRLG_RngTarget::gamecornerabra: case PokemonFRLG_RngTarget::gamecornerclefairy: case PokemonFRLG_RngTarget::gamecornerdratini: + case PokemonFRLG_RngTarget::gamecornerscyther: + case PokemonFRLG_RngTarget::gamecornerpinsir: case PokemonFRLG_RngTarget::gamecornerbug: case PokemonFRLG_RngTarget::gamecornerporygon: return shiny_check_summary(console, context);