diff --git a/SerialPrograms/Source/Pokemon/Pokemon_OriginMarks.cpp b/SerialPrograms/Source/Pokemon/Pokemon_OriginMarks.cpp new file mode 100644 index 0000000000..3574e40c7c --- /dev/null +++ b/SerialPrograms/Source/Pokemon/Pokemon_OriginMarks.cpp @@ -0,0 +1,31 @@ +/* Pokemon Origin Marks + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Pokemon_OriginMarks.h" + +namespace PokemonAutomation{ +namespace Pokemon{ + +const EnumStringMap& ORIGIN_MARK_SLUGS(){ + static EnumStringMap database{ + {OriginMark::KALOS, "kalos"}, + {OriginMark::ALOLA, "alola"}, + {OriginMark::GAMEBOY, "gameboy"}, + {OriginMark::GO, "go"}, + {OriginMark::LGPE, "lgpe"}, + {OriginMark::GALAR, "galar"}, + {OriginMark::BDSP, "bdsp"}, + {OriginMark::LA, "la"}, + {OriginMark::SV, "sv"}, + {OriginMark::LZA, "lza"}, + {OriginMark::NONE, "none"} + }; + return database; +} + + +} +} diff --git a/SerialPrograms/Source/Pokemon/Pokemon_OriginMarks.h b/SerialPrograms/Source/Pokemon/Pokemon_OriginMarks.h new file mode 100644 index 0000000000..be1cc0f312 --- /dev/null +++ b/SerialPrograms/Source/Pokemon/Pokemon_OriginMarks.h @@ -0,0 +1,34 @@ +/* Pokemon Origin Marks + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_Pokemon_OriginMarks_H +#define PokemonAutomation_Pokemon_OriginMarks_H + +#include "Common/Cpp/EnumStringMap.h" + +namespace PokemonAutomation{ +namespace Pokemon{ + + +enum class OriginMark{ + KALOS, + ALOLA, + GAMEBOY, + GO, + LGPE, + GALAR, + BDSP, + LA, + SV, + LZA, + NONE, +}; +const EnumStringMap& ORIGIN_MARK_SLUGS(); + + +} +} +#endif diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_OriginMarkReader.cpp b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_OriginMarkReader.cpp new file mode 100644 index 0000000000..e7a6cffe8b --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_OriginMarkReader.cpp @@ -0,0 +1,282 @@ +/* Origin Mark Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include +#include +#include "CommonTools/Images/WaterfillUtilities.h" +#include "CommonFramework/ImageTools/ImageStats.h" +#include "CommonTools/DetectedBoxes.h" +#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "CommonTools/ImageMatch/ExactImageMatcher.h" +#include "Kernels/Waterfill/Kernels_Waterfill_Types.h" +#include "PokemonHome_OriginMarkReader.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + +class MarkIconMatcher : public ImageMatch::WaterfillTemplateMatcher{ +public: + MarkIconMatcher( + const char* path, + std::string name, + Color min_color, Color max_color, + size_t min_area, + double max_rmsd, + std::vector> filters, + double area_ratio_override = 0 + ) + : WaterfillTemplateMatcher(path, min_color, max_color, min_area) + , m_name(std::move(name)) + , m_max_rmsd(max_rmsd) + , m_filters(std::move(filters)) + { + if (area_ratio_override != 0){ + m_area_ratio = area_ratio_override; + } + } + + const std::string& name() const{ + return m_name; + } + double max_rmsd() const{ + return m_max_rmsd; + } + const std::vector>& filters() const{ + return m_filters; + } + +private: + std::string m_name; + double m_max_rmsd; + std::vector> m_filters; +}; + +const MarkIconMatcher& MarkIcon_Kalos(){ + static MarkIconMatcher ret( + "PokemonHome/OriginMarks/kalos.png", + "kalos", + Color(0xff61645c), Color(0xff646664), + 10, + 40.0, + { + {0xff61645c, 0xff646664}, + } + ); + return ret; +} + +const MarkIconMatcher& MarkIcon_Alola(){ + static MarkIconMatcher ret( + "PokemonHome/OriginMarks/alola.png", + "alola", + Color(0xff5f6456), Color(0xff6d706b), + 10, + 35.0, + { + {0xff5f6456, 0xff6d706b}, + } + ); + return ret; +} + +const MarkIconMatcher& MarkIcon_Go(){ + static MarkIconMatcher ret( + "PokemonHome/OriginMarks/go.png", + "go", + Color(0xff5c644e), Color(0xff798073), + 10, + 45.0, + { + {0xff5c644e, 0xff798073}, + } + ); + return ret; +} + +const MarkIconMatcher& MarkIcon_Lgpe(){ + static MarkIconMatcher ret( + "PokemonHome/OriginMarks/lgpe.png", + "lgpe", + Color(0xff616459), Color(0xff676d64), + 10, + 20.0, + { + {0xff616459, 0xff676d64}, + } + ); + return ret; +} + +const MarkIconMatcher& MarkIcon_Galar(){ + static MarkIconMatcher ret( + "PokemonHome/OriginMarks/galar.png", + "galar", + Color(0xff5e6453), Color(0xff757870), + 10, + 45.0, + { + {0xff5e6453, 0xff757870}, + } + ); + return ret; +} + +const MarkIconMatcher& MarkIcon_Bdsp(){ + static MarkIconMatcher ret( + "PokemonHome/OriginMarks/bdsp.png", + "bdsp", + Color(0xff5f6457), Color(0xff767972), + 10, + 45.0, + { + {0xff5f6457, 0xff767972}, + } + ); + return ret; +} + +const MarkIconMatcher& MarkIcon_La(){ + static MarkIconMatcher ret( + "PokemonHome/OriginMarks/la.png", + "la", + Color(0xff606459), Color(0xff686a65), + 10, + 30.0, + { + {0xff606459, 0xff686a65}, + } + ); + return ret; +} + +const MarkIconMatcher& MarkIcon_Sv(){ + static MarkIconMatcher ret( + "PokemonHome/OriginMarks/sv.png", + "sv", + Color(0xff5d6450), Color(0xff747771), + 10, + 50.0, + { + {0xff5d6450, 0xff747771}, + } + ); + return ret; +} + +const MarkIconMatcher& MarkIcon_Lza(){ + static MarkIconMatcher ret( + "PokemonHome/OriginMarks/lza.png", + "lza", + Color(0xff61645b), Color(0xff646764), + 10, + 20.0, + { + {0xff61645b, 0xff646764}, + } + ); + return ret; +} + +//TODO: GAMEBOY + +const std::vector& ALL_ORIGIN_MARK_MATCHERS(){ + static const std::vector matchers = { + &MarkIcon_Kalos(), + &MarkIcon_Alola(), + &MarkIcon_Go(), + &MarkIcon_Lgpe(), + &MarkIcon_Galar(), + &MarkIcon_Bdsp(), + &MarkIcon_La(), + &MarkIcon_Sv(), + &MarkIcon_Lza(), + }; + return matchers; +} + +const MarkIconMatcher& get_mark_icon_matcher(OriginMark mark){ + switch (mark){ + case OriginMark::KALOS: + return MarkIcon_Kalos(); + case OriginMark::ALOLA: + return MarkIcon_Alola(); + case OriginMark::GO: + return MarkIcon_Go(); + case OriginMark::LGPE: + return MarkIcon_Lgpe(); + case OriginMark::GALAR: + return MarkIcon_Galar(); + case OriginMark::BDSP: + return MarkIcon_Bdsp(); + case OriginMark::LA: + return MarkIcon_La(); + case OriginMark::SV: + return MarkIcon_Sv(); + case OriginMark::LZA: + return MarkIcon_Lza(); + default: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid origin mark: " + std::to_string((int)mark)); + } +} + +OriginMark origin_mark_from_slug(const std::string& slug){ + for (const auto& item : Pokemon::ORIGIN_MARK_SLUGS()){ + if (item.second == slug){ + return item.first; + } + } + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid origin mark slug: " + slug); +} + +OriginMark OriginMarkReader::read_mark( + const ImageViewRGB32& original_screen, + const ImageFloatBox& box +){ + const double screen_rel_size = (original_screen.height() / 1080.0); + const double screen_rel_size_2 = screen_rel_size * screen_rel_size; + + const double min_area_1080p = 400; + const size_t min_area = size_t(screen_rel_size_2 * min_area_1080p); + + ImageViewRGB32 image = extract_box_reference(original_screen, box); + + m_last_detected.clear(); + + std::multimap> candidates; + + for (const MarkIconMatcher* matcher : ALL_ORIGIN_MARK_MATCHERS()){ + match_template_by_waterfill( + original_screen.size(), + image, + *matcher, + matcher->filters(), + { min_area, SIZE_MAX }, + matcher->max_rmsd(), + [&](Kernels::Waterfill::WaterfillObject& object) -> bool { + + m_last_detected.emplace_back( + DetectedBox{ + matcher->name(), + translate_to_parent(original_screen, box, object) + } + ); + return true; + } + ); + + if (!m_last_detected.empty()){ + return origin_mark_from_slug(m_last_detected[0].name); + } + } + + return OriginMark::NONE; +} + + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_OriginMarkReader.h b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_OriginMarkReader.h new file mode 100644 index 0000000000..2d817ea989 --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_OriginMarkReader.h @@ -0,0 +1,38 @@ +/* Origin Mark Reader + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonHome_OriginMarkReader_H +#define PokemonAutomation_PokemonHome_OriginMarkReader_H + +#include +#include "CommonFramework/ImageTypes/ImageViewRGB32.h" +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonTools/DetectedBoxes.h" +#include "Pokemon/Pokemon_OriginMarks.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + + +using Pokemon::OriginMark; + +class OriginMarkReader{ +public: + OriginMark read_mark( + const ImageViewRGB32& original_screen, + const ImageFloatBox& box + ); + +private: + std::vector m_last_detected; + +}; + +} +} +} +#endif \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SelectionArrowDetector.cpp b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SelectionArrowDetector.cpp new file mode 100644 index 0000000000..58c472e3bc --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SelectionArrowDetector.cpp @@ -0,0 +1,108 @@ +/* Selection Arrow Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#include "Common/Cpp/Exceptions.h" +#include "CommonTools/ImageMatch/WaterfillTemplateMatcher.h" +#include "CommonTools/Images/WaterfillUtilities.h" +#include "Kernels/Waterfill/Kernels_Waterfill_Types.h" +#include "PokemonHome_SelectionArrowDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + + +class SelectionArrowMatcher : public ImageMatch::WaterfillTemplateMatcher{ +public: + // The Pokemon Home arrows are red on a light background. + SelectionArrowMatcher(const char* path) + : WaterfillTemplateMatcher( + path, + Color(0xffa00000), Color(0xffffc0c0), 100 + ) + { + m_aspect_ratio_lower = 0.9; + m_aspect_ratio_upper = 1.1; + m_area_ratio_lower = 0.85; + m_area_ratio_upper = 1.1; + } + + static const SelectionArrowMatcher& matcher(SelectionArrowType type){ + switch (type){ + case SelectionArrowType::RIGHT: + return RIGHT_ARROW(); + case SelectionArrowType::DOWN: + return DOWN_ARROW(); + default: + throw InternalProgramError(nullptr, PA_CURRENT_FUNCTION, "Invalid enum."); + } + } + static const SelectionArrowMatcher& RIGHT_ARROW(){ + static SelectionArrowMatcher matcher("PokemonHome/SelectionArrows/SelectionArrowRight.png"); + return matcher; + } + static const SelectionArrowMatcher& DOWN_ARROW(){ + static SelectionArrowMatcher matcher("PokemonHome/SelectionArrows/SelectionArrowDown.png"); + return matcher; + } +}; + +SelectionArrowDetector::SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + SelectionArrowType type, + const ImageFloatBox& box +) + : m_color(color) + , m_overlay(overlay) + , m_type(type) + , m_arrow_box(box) +{} +void SelectionArrowDetector::make_overlays(VideoOverlaySet& items) const{ + items.add(m_color, m_arrow_box); +} +bool SelectionArrowDetector::detect(const ImageViewRGB32& screen){ + double screen_rel_size = (screen.height() / 1080.0); + double screen_rel_size_2 = screen_rel_size * screen_rel_size; + + double min_area_1080p = 700; + double rmsd_threshold = 80; + size_t min_area = size_t(screen_rel_size_2 * min_area_1080p); + + const std::vector> FILTERS = { + {0xff800000, 0xffff9090}, + {0xffa00000, 0xffffa0a0}, + {0xffc00000, 0xffffc0c0}, + {0xffd00000, 0xffffd0d0}, + }; + + bool found = match_template_by_waterfill( + screen.size(), + extract_box_reference(screen, m_arrow_box), + SelectionArrowMatcher::matcher(m_type), + FILTERS, + {min_area, SIZE_MAX}, + rmsd_threshold, + [&](Kernels::Waterfill::WaterfillObject& object) -> bool { + m_last_detected = translate_to_parent(screen, m_arrow_box, object); + return true; + } + ); + + if (m_overlay){ + if (found){ + m_last_detected_box.emplace(*m_overlay, m_last_detected, COLOR_GREEN); + }else{ + m_last_detected_box.reset(); + } + } + + return found; +} + +} +} +} \ No newline at end of file diff --git a/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SelectionArrowDetector.h b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SelectionArrowDetector.h new file mode 100644 index 0000000000..0f1f41223f --- /dev/null +++ b/SerialPrograms/Source/PokemonHome/Inference/PokemonHome_SelectionArrowDetector.h @@ -0,0 +1,69 @@ +/* Selection Arrow Detector + * + * From: https://github.com/PokemonAutomation/ + * + */ + +#ifndef PokemonAutomation_PokemonHome_SelectionArrowDetector_H +#define PokemonAutomation_PokemonHome_SelectionArrowDetector_H + +#include +#include "CommonFramework/ImageTools/ImageBoxes.h" +#include "CommonFramework/VideoPipeline/VideoOverlayScopes.h" +#include "CommonTools/VisualDetector.h" + +namespace PokemonAutomation{ +namespace NintendoSwitch{ +namespace PokemonHome{ + + +enum class SelectionArrowType{ + RIGHT, + DOWN, +}; + + +class SelectionArrowDetector : public StaticScreenDetector{ +public: + SelectionArrowDetector( + Color color, + VideoOverlay* overlay, + SelectionArrowType type, + const ImageFloatBox& box + ); + + const ImageFloatBox& last_detected() const{ return m_last_detected; } + + virtual void make_overlays(VideoOverlaySet& items) const override; + + virtual bool detect(const ImageViewRGB32& screen) override; + +private: + friend class SelectionArrowWatcher; + + const Color m_color; + VideoOverlay* m_overlay; + const SelectionArrowType m_type; + const ImageFloatBox m_arrow_box; + + ImageFloatBox m_last_detected; + std::optional m_last_detected_box; +}; +class SelectionArrowWatcher : public DetectorToFinder{ +public: + SelectionArrowWatcher( + Color color, + VideoOverlay* overlay, + SelectionArrowType type, + const ImageFloatBox& box, + std::chrono::milliseconds hold_duration = std::chrono::milliseconds(250) + ) + : DetectorToFinder("SelectionArrowWatcher", hold_duration, color, overlay, type, box) + {} +}; + + +} +} +} +#endif diff --git a/SerialPrograms/cmake/SourceFiles.cmake b/SerialPrograms/cmake/SourceFiles.cmake index 487aa970bf..d72cce0f4b 100644 --- a/SerialPrograms/cmake/SourceFiles.cmake +++ b/SerialPrograms/cmake/SourceFiles.cmake @@ -1283,6 +1283,8 @@ file(GLOB LIBRARY_SOURCES Source/Pokemon/Pokemon_NatureChecker.h Source/Pokemon/Pokemon_Notification.cpp Source/Pokemon/Pokemon_Notification.h + Source/Pokemon/Pokemon_OriginMarks.cpp + Source/Pokemon/Pokemon_OriginMarks.h Source/Pokemon/Pokemon_ShinySparkleSet.cpp Source/Pokemon/Pokemon_ShinySparkleSet.h Source/Pokemon/Pokemon_StatsCalculation.cpp @@ -1579,6 +1581,10 @@ file(GLOB LIBRARY_SOURCES Source/PokemonHome/Inference/PokemonHome_ButtonDetector.h Source/PokemonHome/Inference/PokemonHome_GigantamaxDetector.cpp Source/PokemonHome/Inference/PokemonHome_GigantamaxDetector.h + Source/PokemonHome/Inference/PokemonHome_OriginMarkReader.cpp + Source/PokemonHome/Inference/PokemonHome_OriginMarkReader.h + Source/PokemonHome/Inference/PokemonHome_SelectionArrowDetector.cpp + Source/PokemonHome/Inference/PokemonHome_SelectionArrowDetector.h Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.cpp Source/PokemonHome/Inference/PokemonHome_TeraTypeReader.h Source/PokemonHome/PokemonHome_Panels.cpp